Update: Krystie reporter integration and strategy improvements
This commit is contained in:
+18
-15
@@ -11,30 +11,33 @@
|
||||
},
|
||||
"trading": {
|
||||
"symbols": [
|
||||
"USO", "XLE", "OXY", "CVX", "XOM",
|
||||
"SLB", "HAL", "DVN", "MPC", "VLO"
|
||||
"XLE", "CVX", "XOM"
|
||||
],
|
||||
"forex_symbols": [
|
||||
"USD_JPY", "EUR_JPY", "GBP_JPY", "CAD_JPY",
|
||||
"AUD_JPY", "EUR_USD", "GBP_USD", "USD_CAD"
|
||||
],
|
||||
"cycle_interval_seconds": 60,
|
||||
"initial_capital": 100,
|
||||
"target_capital": 1000,
|
||||
"cycle_interval_seconds": 300,
|
||||
"initial_capital": 200000,
|
||||
"target_capital": 250000,
|
||||
"commission_rate": 0.0,
|
||||
"min_trade_value": 3,
|
||||
"min_trade_value": 100,
|
||||
"require_approval": false,
|
||||
"max_position_pct": 12,
|
||||
"max_concurrent_positions": 12
|
||||
"max_position_pct": 10,
|
||||
"max_concurrent_positions": 8,
|
||||
"stop_loss_pct": 2.5,
|
||||
"take_profit_pct": 5.0,
|
||||
"min_backtest_sharpe": 0.5,
|
||||
"max_correlated_positions": 3
|
||||
},
|
||||
"safety": {
|
||||
"max_position_pct": 12,
|
||||
"max_concurrent_positions": 12,
|
||||
"max_daily_trades": 50,
|
||||
"max_daily_loss_pct": 4,
|
||||
"max_total_loss_pct": 15,
|
||||
"min_trade_value": 3,
|
||||
"initial_capital": 100
|
||||
"max_position_pct": 10,
|
||||
"max_concurrent_positions": 8,
|
||||
"max_daily_trades": 30,
|
||||
"max_daily_loss_pct": 2,
|
||||
"max_total_loss_pct": 10,
|
||||
"min_trade_value": 100,
|
||||
"initial_capital": 200000
|
||||
},
|
||||
"rl": {
|
||||
"gamma": 0.97,
|
||||
|
||||
Executable
+235
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enhanced BIGGFISH Daily Reporter
|
||||
Redesigned for readability and actionable insights.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Load status data
|
||||
def load_status() -> Dict:
|
||||
status_file = Path("/opt/biggfish/src/data/krystie-status.json")
|
||||
if not status_file.exists():
|
||||
return {}
|
||||
with open(status_file) as f:
|
||||
return json.load(f)
|
||||
|
||||
def load_events() -> List[Dict]:
|
||||
events_file = Path("/opt/biggfish/src/data/krystie-events.json")
|
||||
if not events_file.exists():
|
||||
return []
|
||||
with open(events_file) as f:
|
||||
data = json.load(f)
|
||||
return data.get('events', [])
|
||||
|
||||
def format_currency(val: float) -> str:
|
||||
"""Format with K/M suffix for readability"""
|
||||
if abs(val) >= 1_000_000:
|
||||
return f"${val/1_000_000:.2f}M"
|
||||
elif abs(val) >= 1_000:
|
||||
return f"${val/1_000:.1f}K"
|
||||
else:
|
||||
return f"${val:.2f}"
|
||||
|
||||
def format_pnl(val: float, pct: Optional[float] = None) -> str:
|
||||
"""Format P&L with color emoji"""
|
||||
emoji = "🟢" if val >= 0 else "🔴"
|
||||
base = f"{emoji} {format_currency(val)}"
|
||||
if pct is not None:
|
||||
base += f" ({pct:+.2f}%)"
|
||||
return base
|
||||
|
||||
def get_position_summary(positions: List[Dict]) -> str:
|
||||
"""Summarize positions in a compact way"""
|
||||
if not positions:
|
||||
return "No positions"
|
||||
|
||||
total_unrealized = sum(p.get('unrealized_pnl', 0) for p in positions)
|
||||
winning = [p for p in positions if p.get('unrealized_pnl', 0) > 0]
|
||||
losing = [p for p in positions if p.get('unrealized_pnl', 0) < 0]
|
||||
|
||||
parts = []
|
||||
if winning:
|
||||
parts.append(f"{len(winning)} ✅")
|
||||
if losing:
|
||||
parts.append(f"{len(losing)} ❌")
|
||||
|
||||
status = " | ".join(parts) if parts else f"{len(positions)} flat"
|
||||
return f"{status} → {format_currency(total_unrealized)}"
|
||||
|
||||
def get_top_movers(positions: List[Dict], limit: int = 3) -> List[str]:
|
||||
"""Get top winning and losing positions"""
|
||||
if not positions:
|
||||
return []
|
||||
|
||||
sorted_pos = sorted(positions, key=lambda p: p.get('unrealized_pnl', 0), reverse=True)
|
||||
lines = []
|
||||
|
||||
# Top winners
|
||||
for p in sorted_pos[:limit]:
|
||||
pnl = p.get('unrealized_pnl', 0)
|
||||
if pnl > 0:
|
||||
lines.append(f" ✅ {p['symbol']}: {format_currency(pnl)}")
|
||||
|
||||
# Top losers
|
||||
for p in sorted_pos[-limit:]:
|
||||
pnl = p.get('unrealized_pnl', 0)
|
||||
if pnl < 0:
|
||||
lines.append(f" ❌ {p['symbol']}: {format_currency(pnl)}")
|
||||
|
||||
return lines
|
||||
|
||||
def get_recent_trades(events: List[Dict], hours: int = 24) -> List[Dict]:
|
||||
"""Get trades from last N hours"""
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
|
||||
trades = []
|
||||
|
||||
for event in events:
|
||||
if event.get('type') != 'trade':
|
||||
continue
|
||||
|
||||
ts_str = event.get('timestamp')
|
||||
if not ts_str:
|
||||
continue
|
||||
|
||||
try:
|
||||
ts = datetime.fromisoformat(ts_str.replace('Z', '+00:00'))
|
||||
if ts >= cutoff:
|
||||
trades.append(event)
|
||||
except:
|
||||
continue
|
||||
|
||||
return trades
|
||||
|
||||
def format_learning_insight(learning: Dict) -> str:
|
||||
"""Translate learning metrics into plain English"""
|
||||
epsilon = learning.get('rl_epsilon', 1.0)
|
||||
generation = learning.get('ga_generation', 0)
|
||||
fitness = learning.get('ga_best_fitness', 0)
|
||||
|
||||
# Epsilon interpretation
|
||||
if epsilon > 0.5:
|
||||
mode = "🎲 Exploring heavily"
|
||||
elif epsilon > 0.2:
|
||||
mode = "🔀 Balanced exploration"
|
||||
elif epsilon > 0.05:
|
||||
mode = "🎯 Mostly exploiting"
|
||||
else:
|
||||
mode = "🔒 Pure exploitation"
|
||||
|
||||
# Fitness interpretation
|
||||
if fitness > 20:
|
||||
perf = "Excellent"
|
||||
elif fitness > 10:
|
||||
perf = "Good"
|
||||
elif fitness > 5:
|
||||
perf = "Developing"
|
||||
else:
|
||||
perf = "Early stage"
|
||||
|
||||
return f"{mode} | Gen {generation} ({perf})"
|
||||
|
||||
def format_daily_report(status: Dict, events: List[Dict]) -> str:
|
||||
"""Format a clean, scannable daily report"""
|
||||
portfolio = status.get('portfolio', {})
|
||||
positions = status.get('positions', [])
|
||||
learning = status.get('learning', {})
|
||||
config = status.get('config', {})
|
||||
markets = status.get('markets', {})
|
||||
|
||||
equity = portfolio.get('equity', 0)
|
||||
initial = config.get('initial_capital', 200000)
|
||||
target = config.get('target_capital', 250000)
|
||||
day_pnl = portfolio.get('day_pnl', 0)
|
||||
day_pnl_pct = portfolio.get('day_pnl_pct', 0)
|
||||
total_pnl = equity - initial
|
||||
total_pnl_pct = (total_pnl / initial * 100) if initial > 0 else 0
|
||||
|
||||
# Recent trades
|
||||
recent_trades = get_recent_trades(events, hours=24)
|
||||
wins = [t for t in recent_trades if t.get('pnl', 0) > 0]
|
||||
losses = [t for t in recent_trades if t.get('pnl', 0) < 0]
|
||||
trade_pnl = sum(t.get('pnl', 0) for t in recent_trades)
|
||||
|
||||
lines = []
|
||||
lines.append("🐟 <b>BIGGFISH Daily Report</b>")
|
||||
lines.append(f"📅 {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
|
||||
lines.append("")
|
||||
|
||||
# === PORTFOLIO SNAPSHOT ===
|
||||
lines.append(f"💰 <b>{format_currency(equity)}</b> equity")
|
||||
lines.append(f"📊 Today: {format_pnl(day_pnl, day_pnl_pct)}")
|
||||
lines.append(f"📈 Total: {format_pnl(total_pnl, total_pnl_pct)}")
|
||||
|
||||
# Progress to goal
|
||||
progress = (equity - initial) / (target - initial) * 100 if target > initial else 0
|
||||
if progress < 0:
|
||||
progress_text = f"⚠️ <b>Down {abs(progress):.1f}%</b> from start"
|
||||
elif progress >= 100:
|
||||
progress_text = f"🎯 <b>GOAL REACHED!</b>"
|
||||
else:
|
||||
progress_text = f"🎯 {progress:.1f}% to goal ({format_currency(target - equity)} left)"
|
||||
lines.append(progress_text)
|
||||
lines.append("")
|
||||
|
||||
# === TODAY'S ACTIVITY ===
|
||||
lines.append("<b>📋 Today's Trading</b>")
|
||||
if recent_trades:
|
||||
win_rate = (len(wins) / len(recent_trades) * 100) if recent_trades else 0
|
||||
lines.append(f" {len(recent_trades)} trades | {win_rate:.0f}% win rate")
|
||||
lines.append(f" {format_pnl(trade_pnl)}")
|
||||
|
||||
# Show significant trades
|
||||
significant = sorted(recent_trades, key=lambda t: abs(t.get('pnl', 0)), reverse=True)[:3]
|
||||
for t in significant:
|
||||
pnl = t.get('pnl', 0)
|
||||
if abs(pnl) > 5: # Only show trades > $5 P&L
|
||||
emoji = "✅" if pnl > 0 else "❌"
|
||||
symbol = t.get('symbol', '?')
|
||||
lines.append(f" {emoji} {symbol}: {format_currency(pnl)}")
|
||||
else:
|
||||
lines.append(" No trades today")
|
||||
lines.append("")
|
||||
|
||||
# === OPEN POSITIONS ===
|
||||
lines.append(f"<b>📊 Positions: {len(positions)}</b>")
|
||||
if positions:
|
||||
lines.append(f" {get_position_summary(positions)}")
|
||||
|
||||
# Show top movers
|
||||
movers = get_top_movers(positions, limit=2)
|
||||
if movers:
|
||||
lines.extend(movers)
|
||||
else:
|
||||
lines.append(" All flat")
|
||||
lines.append("")
|
||||
|
||||
# === LEARNING STATUS ===
|
||||
lines.append("<b>🧠 Learning</b>")
|
||||
lines.append(f" {format_learning_insight(learning)}")
|
||||
lines.append("")
|
||||
|
||||
# === MARKET STATUS ===
|
||||
stock_status = "🟢 Open" if markets.get('stocks') == 'OPEN' else "🔴 Closed"
|
||||
forex_status = "🟢 Open" if markets.get('forex') == 'OPEN' else "🔴 Closed"
|
||||
lines.append(f"<b>🏦 Markets:</b> Stocks {stock_status} | Forex {forex_status}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def main():
|
||||
status = load_status()
|
||||
events = load_events()
|
||||
|
||||
if not status:
|
||||
print("❌ Could not load status data")
|
||||
sys.exit(1)
|
||||
|
||||
report = format_daily_report(status, events)
|
||||
print(report)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
+330
-425
@@ -1,503 +1,408 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"time": "2026-03-12T22:53:54Z",
|
||||
"time": "2026-03-17T04:50:24Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50334,
|
||||
"fitness": 23.8731
|
||||
"generation": 795,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T22:55:44Z",
|
||||
"time": "2026-03-17T07:50:45Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50349,
|
||||
"fitness": 23.8731
|
||||
"generation": 810,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T22:57:38Z",
|
||||
"time": "2026-03-17T10:52:57Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50364,
|
||||
"fitness": 23.8731
|
||||
"generation": 825,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T22:59:44Z",
|
||||
"time": "2026-03-17T13:57:33Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50379,
|
||||
"fitness": 23.8731
|
||||
"generation": 840,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:01:50Z",
|
||||
"time": "2026-03-17T17:01:13Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50394,
|
||||
"fitness": 23.8731
|
||||
"generation": 855,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:03:52Z",
|
||||
"time": "2026-03-17T20:02:50Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50409,
|
||||
"fitness": 23.8731
|
||||
"generation": 870,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:05:50Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50424,
|
||||
"fitness": 23.8731
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:07:42Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50439,
|
||||
"fitness": 23.8731
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:09:42Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50454,
|
||||
"fitness": 23.8731
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:11:47Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 50469,
|
||||
"fitness": 23.8731
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:13:24Z",
|
||||
"type": "bot_started",
|
||||
"data": {
|
||||
"message": "BIGGFISH autonomous trader started"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:14:39Z",
|
||||
"type": "bot_started",
|
||||
"data": {
|
||||
"message": "BIGGFISH autonomous trader started"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:15:49Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "AUD_USD",
|
||||
"side": "buy",
|
||||
"amount": 27957,
|
||||
"entry_price": 0.70788
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:15:51Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "USD_CAD",
|
||||
"side": "buy",
|
||||
"amount": 14515,
|
||||
"entry_price": 1.36334
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:15:53Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "EUR_GBP",
|
||||
"side": "buy",
|
||||
"amount": 22932,
|
||||
"entry_price": 0.86296
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:15:54Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "USD_CHF",
|
||||
"side": "buy",
|
||||
"amount": 25186,
|
||||
"entry_price": 0.78568
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:15:56Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "NZD_USD",
|
||||
"side": "buy",
|
||||
"amount": 33817,
|
||||
"entry_price": 0.58514
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:16:49Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 15,
|
||||
"fitness": 1.7479
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:16:51Z",
|
||||
"time": "2026-03-17T21:04:19Z",
|
||||
"type": "daily_report",
|
||||
"data": {
|
||||
"equity": 98929.1,
|
||||
"day_pnl": -1070.9,
|
||||
"trades_count": 5
|
||||
"equity": 100000.0,
|
||||
"day_pnl": 0.0,
|
||||
"trades_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:17:44Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "GBP_USD",
|
||||
"side": "buy",
|
||||
"amount": 14823,
|
||||
"entry_price": 1.3348
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:17:47Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "AUD_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.70784,
|
||||
"pnl": -0.56,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:18:12Z",
|
||||
"time": "2026-03-17T23:06:29Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 30,
|
||||
"fitness": 1.8512
|
||||
"generation": 885,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:19:43Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "EUR_USD",
|
||||
"side": "buy",
|
||||
"amount": 17179,
|
||||
"entry_price": 1.1516
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:19:48Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "USD_JPY",
|
||||
"side": "buy",
|
||||
"amount": 124,
|
||||
"entry_price": 159.342
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:19:51Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "USD_CAD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 1.3636,
|
||||
"pnl": 1.89,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:19:56Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "NZD_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.585,
|
||||
"pnl": -2.37,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:20:16Z",
|
||||
"time": "2026-03-18T02:08:20Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 45,
|
||||
"fitness": 1.8512
|
||||
"generation": 900,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:21:44Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "GBP_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 1.33478,
|
||||
"pnl": -0.3,
|
||||
"pnl_pct": -0.0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:21:47Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "USD_CAD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 1.36348,
|
||||
"pnl": 0.51,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:21:49Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "EUR_GBP",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.86288,
|
||||
"pnl": -0.92,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:22:12Z",
|
||||
"time": "2026-03-18T05:10:29Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 60,
|
||||
"fitness": 1.8512
|
||||
"generation": 915,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:23:49Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "USD_CAD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 1.36355,
|
||||
"pnl": 0.76,
|
||||
"pnl_pct": 0.02,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:23:53Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "USD_CHF",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.78586,
|
||||
"pnl": 2.27,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:24:15Z",
|
||||
"time": "2026-03-18T08:13:40Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 75,
|
||||
"fitness": 1.8512
|
||||
"generation": 930,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:25:45Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "GBP_USD",
|
||||
"side": "buy",
|
||||
"amount": 14822,
|
||||
"entry_price": 1.33468
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:25:51Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "EUR_GBP",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.8628,
|
||||
"pnl": -0.92,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:26:10Z",
|
||||
"time": "2026-03-18T11:20:37Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 90,
|
||||
"fitness": 1.8512
|
||||
"generation": 945,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:27:45Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "USD_JPY",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 159.35,
|
||||
"pnl": 0.99,
|
||||
"pnl_pct": 0.01,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:28:10Z",
|
||||
"time": "2026-03-18T14:33:37Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 105,
|
||||
"fitness": 1.8512
|
||||
"generation": 960,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:29:45Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "GBP_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 1.33488,
|
||||
"pnl": 2.96,
|
||||
"pnl_pct": 0.01,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:29:46Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "USD_JPY",
|
||||
"side": "buy",
|
||||
"amount": 124,
|
||||
"entry_price": 159.326
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:29:48Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "AUD_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.70756,
|
||||
"pnl": -2.24,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:29:52Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "USD_CHF",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.78578,
|
||||
"pnl": 1.26,
|
||||
"pnl_pct": 0.01,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:29:54Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "NZD_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 0.58492,
|
||||
"pnl": -3.72,
|
||||
"pnl_pct": -0.04,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:30:25Z",
|
||||
"time": "2026-03-18T17:34:38Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 120,
|
||||
"fitness": 1.8615
|
||||
"generation": 975,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:31:43Z",
|
||||
"type": "trade_close",
|
||||
"data": {
|
||||
"symbol": "EUR_USD",
|
||||
"side": "sell",
|
||||
"entry_price": null,
|
||||
"exit_price": 1.15166,
|
||||
"pnl": 0.52,
|
||||
"pnl_pct": 0,
|
||||
"exit_reason": "signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:31:45Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "GBP_USD",
|
||||
"side": "buy",
|
||||
"amount": 14821,
|
||||
"entry_price": 1.33481
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:31:48Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "USD_CAD",
|
||||
"side": "buy",
|
||||
"amount": 14506,
|
||||
"entry_price": 1.36368
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:31:51Z",
|
||||
"type": "trade_open",
|
||||
"data": {
|
||||
"symbol": "USD_CHF",
|
||||
"side": "buy",
|
||||
"amount": 25174,
|
||||
"entry_price": 0.78579
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-12T23:32:10Z",
|
||||
"time": "2026-03-18T20:38:19Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 135,
|
||||
"fitness": 1.8615
|
||||
"generation": 990,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-18T21:02:33Z",
|
||||
"type": "daily_report",
|
||||
"data": {
|
||||
"equity": 100000.0,
|
||||
"day_pnl": 0.0,
|
||||
"trades_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-18T23:43:41Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1005,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T03:03:41Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1020,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T06:16:54Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1035,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T09:19:52Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1050,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T12:25:40Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1065,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T15:37:18Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1080,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T18:49:30Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1095,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T21:00:43Z",
|
||||
"type": "daily_report",
|
||||
"data": {
|
||||
"equity": 100000.0,
|
||||
"day_pnl": 0.0,
|
||||
"trades_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-19T21:58:19Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1110,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T01:10:04Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1125,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T04:21:59Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1140,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T07:31:14Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1155,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T10:39:21Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1170,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T13:42:33Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1185,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T17:01:16Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1200,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T20:04:07Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1215,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T21:05:20Z",
|
||||
"type": "daily_report",
|
||||
"data": {
|
||||
"equity": 100000.0,
|
||||
"day_pnl": 0.0,
|
||||
"trades_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-20T23:13:45Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1230,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T02:21:43Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1245,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T05:25:40Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1260,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T08:32:28Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1275,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T11:35:57Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1290,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T14:57:29Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1305,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T18:03:03Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1320,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-21T21:09:18Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1335,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T00:28:43Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1350,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T03:31:00Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1365,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T06:31:11Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1380,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T09:30:53Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1395,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T12:39:52Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1410,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T15:51:18Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1425,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T18:58:41Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1440,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T21:07:26Z",
|
||||
"type": "daily_report",
|
||||
"data": {
|
||||
"equity": 100000.0,
|
||||
"day_pnl": 0.0,
|
||||
"trades_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"time": "2026-03-22T22:10:40Z",
|
||||
"type": "ga_milestone",
|
||||
"data": {
|
||||
"generation": 1455,
|
||||
"fitness": 27.5509
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
{
|
||||
"updated_at": "2026-03-12T23:32:11Z",
|
||||
"uptime_hours": 0.3,
|
||||
"updated_at": "2026-03-22T23:17:48Z",
|
||||
"uptime_hours": 144.6,
|
||||
"markets": {
|
||||
"stocks": "CLOSED",
|
||||
"forex": "OPEN"
|
||||
},
|
||||
"portfolio": {
|
||||
"equity": 98905.3095,
|
||||
"cash": 98924.0431,
|
||||
"buying_power": 96190.7269,
|
||||
"portfolio_value": 98905.3095,
|
||||
"long_market_value": -18.733599999992293,
|
||||
"day_pnl": -1094.578999999998,
|
||||
"day_pnl_pct": -1.094578999999998
|
||||
"equity": 198763.9988,
|
||||
"cash": 198734.41379999998,
|
||||
"buying_power": 297978.6476,
|
||||
"portfolio_value": 198763.9988,
|
||||
"long_market_value": 29.585000000006403,
|
||||
"day_pnl": 29.585,
|
||||
"day_pnl_pct": 0.0148867020232205
|
||||
},
|
||||
"positions": [
|
||||
{
|
||||
"symbol": "NZD_USD",
|
||||
"qty": 16,
|
||||
"entry_price": 0.58526,
|
||||
"current_price": 0.58482875,
|
||||
"unrealized_pnl": -0.0069
|
||||
"entry_price": 0.58522,
|
||||
"current_price": 0.5832075,
|
||||
"unrealized_pnl": -0.0322
|
||||
},
|
||||
{
|
||||
"symbol": "GBP_JPY",
|
||||
"qty": -2,
|
||||
"entry_price": 211.831,
|
||||
"current_price": 211.83325,
|
||||
"unrealized_pnl": -0.0045
|
||||
},
|
||||
{
|
||||
"symbol": "AUD_USD",
|
||||
"qty": 7018,
|
||||
"qty": 3524,
|
||||
"entry_price": 0.70794,
|
||||
"current_price": 0.7074600056996295,
|
||||
"unrealized_pnl": -3.3686
|
||||
"current_price": 0.701260005675369,
|
||||
"unrealized_pnl": -23.5403
|
||||
},
|
||||
{
|
||||
"symbol": "USD_JPY",
|
||||
"qty": 140,
|
||||
"entry_price": 159.338,
|
||||
"current_price": 159.33775642857142,
|
||||
"unrealized_pnl": -0.0341
|
||||
"qty": 16,
|
||||
"entry_price": 159.08,
|
||||
"current_price": 159.08028125,
|
||||
"unrealized_pnl": 0.0045
|
||||
},
|
||||
{
|
||||
"symbol": "USD_CHF",
|
||||
"qty": 25181,
|
||||
"qty": 12594,
|
||||
"entry_price": 0.78588,
|
||||
"current_price": 0.7855858536197927,
|
||||
"unrealized_pnl": -7.4069
|
||||
"current_price": 0.7875482944259171,
|
||||
"unrealized_pnl": 21.0105
|
||||
},
|
||||
{
|
||||
"symbol": "GBP_USD",
|
||||
"qty": 14839,
|
||||
"entry_price": 1.3349,
|
||||
"current_price": 1.3347701260192735,
|
||||
"unrealized_pnl": -1.9272
|
||||
"qty": 19,
|
||||
"entry_price": 1.33151,
|
||||
"current_price": 1.3331626315789473,
|
||||
"unrealized_pnl": 0.0314
|
||||
},
|
||||
{
|
||||
"symbol": "USD_CAD",
|
||||
"qty": 14526,
|
||||
"entry_price": 1.36378,
|
||||
"current_price": 1.3635518642434257,
|
||||
"unrealized_pnl": -3.3139
|
||||
"qty": 22,
|
||||
"entry_price": 1.37093,
|
||||
"current_price": 1.3716709090909092,
|
||||
"unrealized_pnl": 0.0163
|
||||
},
|
||||
{
|
||||
"symbol": "EUR_GBP",
|
||||
"qty": 5748,
|
||||
"entry_price": 0.86301,
|
||||
"current_price": 0.8626343562978428,
|
||||
"unrealized_pnl": -2.1592
|
||||
"current_price": 0.8685684203201114,
|
||||
"unrealized_pnl": 31.9498
|
||||
},
|
||||
{
|
||||
"symbol": "EUR_USD",
|
||||
"qty": 8614,
|
||||
"entry_price": 1.15168,
|
||||
"current_price": 1.1516200046436034,
|
||||
"unrealized_pnl": -0.5168
|
||||
"qty": 25,
|
||||
"entry_price": 1.15018,
|
||||
"current_price": 1.15616,
|
||||
"unrealized_pnl": 0.1495
|
||||
}
|
||||
],
|
||||
"learning": {
|
||||
"ga_generation": 135,
|
||||
"ga_best_fitness": 1.8615,
|
||||
"rl_epsilon": 0.7471,
|
||||
"rl_experiences": 710,
|
||||
"rl_loss": 0.000668
|
||||
"ga_generation": 1455,
|
||||
"ga_best_fitness": 27.5509,
|
||||
"rl_epsilon": 0.08,
|
||||
"rl_experiences": 100000,
|
||||
"rl_loss": 0.007947
|
||||
},
|
||||
"today_summary": {
|
||||
"trades_count": 22,
|
||||
"trades_count": 0,
|
||||
"wins": 0,
|
||||
"losses": 15,
|
||||
"total_pnl": -239.08
|
||||
"losses": 0,
|
||||
"total_pnl": 0
|
||||
},
|
||||
"config": {
|
||||
"stock_symbols": [
|
||||
"SOUN",
|
||||
"MARA",
|
||||
"RIOT",
|
||||
"BBAI",
|
||||
"PLTR",
|
||||
"HOOD",
|
||||
"SOFI",
|
||||
"COIN",
|
||||
"RBLX",
|
||||
"DKNG"
|
||||
"XLE",
|
||||
"CVX",
|
||||
"XOM"
|
||||
],
|
||||
"forex_symbols": [
|
||||
"USD_JPY",
|
||||
"EUR_JPY",
|
||||
"GBP_JPY",
|
||||
"CAD_JPY",
|
||||
"AUD_JPY",
|
||||
"EUR_USD",
|
||||
"GBP_USD",
|
||||
"USD_JPY",
|
||||
"AUD_USD",
|
||||
"USD_CAD",
|
||||
"EUR_GBP",
|
||||
"USD_CHF",
|
||||
"NZD_USD"
|
||||
"USD_CAD"
|
||||
],
|
||||
"initial_capital": 100000,
|
||||
"target_capital": 1000000
|
||||
"initial_capital": 200000,
|
||||
"target_capital": 250000
|
||||
}
|
||||
}
|
||||
+14
-1
@@ -221,7 +221,20 @@ class DataStore:
|
||||
rows = self.conn.execute(
|
||||
"SELECT * FROM trades WHERE status='open' ORDER BY entry_time DESC"
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
positions = []
|
||||
for r in rows:
|
||||
pos = dict(r)
|
||||
# Parse metadata JSON if it exists
|
||||
if pos.get('metadata'):
|
||||
try:
|
||||
pos['metadata'] = json.loads(pos['metadata'])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pos['metadata'] = {}
|
||||
else:
|
||||
pos['metadata'] = {}
|
||||
positions.append(pos)
|
||||
return positions
|
||||
|
||||
def close_position(self, trade_id: int, exit_price: float,
|
||||
exit_time: datetime, fees: float = 0):
|
||||
|
||||
+71
-9
@@ -132,6 +132,7 @@ class BiggFishAuto:
|
||||
self.last_dashboard = datetime.min
|
||||
self.last_daily_report = datetime.min
|
||||
self.recent_trades = [] # Last 10 trades for dashboard
|
||||
self.backtest_results = {} # symbol -> {'sharpe': x, 'win_rate': y}
|
||||
|
||||
logger.info("BIGGFISH Autonomous Trader initialized")
|
||||
|
||||
@@ -319,6 +320,13 @@ class BiggFishAuto:
|
||||
|
||||
def _trade_symbol(self, symbol: str):
|
||||
"""Evaluate and potentially trade a single symbol (scalping mode)"""
|
||||
# Filter by backtest Sharpe ratio
|
||||
min_sharpe = self.config['trading'].get('min_backtest_sharpe', 0.5)
|
||||
if symbol in self.backtest_results:
|
||||
if self.backtest_results[symbol]['sharpe'] < min_sharpe:
|
||||
logger.debug(f"Skipping {symbol}: Sharpe {self.backtest_results[symbol]['sharpe']:.2f} < {min_sharpe}")
|
||||
return
|
||||
|
||||
broker = self._get_broker(symbol)
|
||||
executor = self._get_executor(symbol)
|
||||
|
||||
@@ -352,11 +360,26 @@ class BiggFishAuto:
|
||||
raw_df = self.feature_engine.compute(df)
|
||||
if raw_df is not None and len(raw_df) > 0:
|
||||
ga_signal = evaluate_genome_signal(best_genome, raw_df, len(raw_df) - 1)
|
||||
|
||||
# Use config defaults if GA doesn't provide stop-loss/take-profit
|
||||
sl_pct = self.config['trading'].get('stop_loss_pct', 2.5) / 100
|
||||
tp_pct = self.config['trading'].get('take_profit_pct', 5.0) / 100
|
||||
|
||||
# CRITICAL FIX: Convert percentages to absolute prices
|
||||
# Stop-loss BELOW current price for longs, ABOVE for shorts
|
||||
# Take-profit ABOVE current price for longs, BELOW for shorts
|
||||
current_price = broker.get_latest_price(symbol) or 0
|
||||
|
||||
sl_price = current_price * (1 - sl_pct) if current_price > 0 else None
|
||||
tp_price = current_price * (1 + tp_pct) if current_price > 0 else None
|
||||
short_sl_price = current_price * (1 + sl_pct) if current_price > 0 else None # ABOVE for shorts
|
||||
short_tp_price = current_price * (1 - tp_pct) if current_price > 0 else None # BELOW for shorts
|
||||
|
||||
strategy_params = {
|
||||
'stop_loss': ga_signal.get('stop_loss'),
|
||||
'take_profit': ga_signal.get('take_profit'),
|
||||
'short_stop_loss': ga_signal.get('short_stop_loss'),
|
||||
'short_take_profit': ga_signal.get('short_take_profit'),
|
||||
'stop_loss': sl_price,
|
||||
'take_profit': tp_price,
|
||||
'short_stop_loss': short_sl_price,
|
||||
'short_take_profit': short_tp_price,
|
||||
'position_pct': ga_signal.get('position_pct', 0.1),
|
||||
'strategy_id': f"ga_gen{best_genome.generation}",
|
||||
}
|
||||
@@ -458,6 +481,12 @@ class BiggFishAuto:
|
||||
params=best_genome.to_dict(),
|
||||
metrics=result.metrics
|
||||
)
|
||||
# Store backtest result for filtering
|
||||
self.backtest_results[symbol] = {
|
||||
'sharpe': result.metrics['sharpe_ratio'],
|
||||
'win_rate': result.metrics['win_rate'],
|
||||
'total_return': result.metrics['total_return'],
|
||||
}
|
||||
logger.info(f"Backtest {symbol}: Sharpe={result.metrics['sharpe_ratio']:.2f} "
|
||||
f"WR={result.metrics['win_rate']:.0f}% "
|
||||
f"Return={result.metrics['total_return']:.1f}%")
|
||||
@@ -606,18 +635,51 @@ class BiggFishAuto:
|
||||
def _print_dashboard(self):
|
||||
"""Print live console dashboard"""
|
||||
try:
|
||||
portfolio = self.broker.get_portfolio()
|
||||
positions = self.broker.get_positions()
|
||||
# Get Alpaca portfolio
|
||||
alpaca_portfolio = self.broker.get_portfolio()
|
||||
alpaca_positions = self.broker.get_positions()
|
||||
|
||||
# Get OANDA portfolio if available
|
||||
if self.oanda_broker:
|
||||
oanda_portfolio = self.oanda_broker.get_portfolio()
|
||||
oanda_positions = self.oanda_broker.get_positions()
|
||||
|
||||
# Combine portfolios
|
||||
combined_equity = alpaca_portfolio['equity'] + oanda_portfolio['equity']
|
||||
combined_day_pnl = alpaca_portfolio['day_pnl'] + oanda_portfolio['day_pnl']
|
||||
prev_equity = combined_equity - combined_day_pnl
|
||||
|
||||
portfolio = {
|
||||
'equity': combined_equity,
|
||||
'cash': alpaca_portfolio['cash'] + oanda_portfolio['cash'],
|
||||
'buying_power': alpaca_portfolio['buying_power'] + oanda_portfolio['buying_power'],
|
||||
'portfolio_value': alpaca_portfolio['portfolio_value'] + oanda_portfolio['portfolio_value'],
|
||||
'long_market_value': alpaca_portfolio['long_market_value'] + oanda_portfolio['long_market_value'],
|
||||
'day_pnl': combined_day_pnl,
|
||||
'day_pnl_pct': (combined_day_pnl / prev_equity * 100) if prev_equity > 0 else 0,
|
||||
}
|
||||
# Combine positions
|
||||
positions = alpaca_positions + oanda_positions
|
||||
else:
|
||||
portfolio = alpaca_portfolio
|
||||
positions = alpaca_positions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Dashboard error: {e}")
|
||||
return
|
||||
|
||||
equity = portfolio['equity']
|
||||
target = self.config['trading']['target_capital']
|
||||
initial = self.config['trading']['initial_capital']
|
||||
progress = (equity / target) * 100
|
||||
|
||||
# Calculate actual initial capital (both brokers start with $100k each in paper trading)
|
||||
if self.oanda_broker:
|
||||
initial = 200000 # $100k Alpaca + $100k OANDA
|
||||
else:
|
||||
initial = 100000 # $100k Alpaca only
|
||||
|
||||
progress = (equity / target) * 100 if target > 0 else 0
|
||||
total_pnl = equity - initial
|
||||
total_pnl_pct = (total_pnl / initial) * 100
|
||||
total_pnl_pct = (total_pnl / initial) * 100 if initial > 0 else 0
|
||||
|
||||
uptime = datetime.utcnow() - self.start_time if self.start_time else timedelta()
|
||||
hours = int(uptime.total_seconds() // 3600)
|
||||
|
||||
+28
-7
@@ -140,17 +140,31 @@ def _evaluate_genome_worker(genome_dict: Dict, candles_dict: Dict[str, Dict],
|
||||
max_dd = m.get('max_drawdown', 0)
|
||||
total_trades = m.get('total_trades', 0)
|
||||
win_rate = m.get('win_rate', 0) / 100.0 # 0-1
|
||||
|
||||
total_return = m.get('total_return', 0) / 100.0 # Convert % to decimal
|
||||
|
||||
# CRITICAL FIX: Profit/return MUST be the primary fitness component
|
||||
# Without this, GA evolves "good metrics" that lose money!
|
||||
|
||||
# Return component (most important): exponential reward for profit, penalty for loss
|
||||
if total_return > 0:
|
||||
return_score = 1.0 + (total_return * 10.0) # Reward profit heavily
|
||||
else:
|
||||
return_score = max(0.01, 1.0 + (total_return * 20.0)) # Penalize losses even harder
|
||||
|
||||
dd_penalty = max(1 - max_dd / 100, 0)
|
||||
# Scalping: reward higher trade frequency more aggressively
|
||||
trade_bonus = math.sqrt(max(total_trades, 0))
|
||||
# Scalping: reward higher trade frequency (but less than before)
|
||||
trade_bonus = 1.0 + math.log1p(max(total_trades, 0)) * 0.1
|
||||
# Bonus for win rate > 50%
|
||||
wr_bonus = 1.0 + max(0, win_rate - 0.5) * 0.5
|
||||
wr_bonus = 1.0 + max(0, win_rate - 0.5) * 0.3
|
||||
|
||||
# Sharpe bonus (risk-adjusted return quality)
|
||||
sharpe_bonus = 1.0 + (sharpe * 0.2)
|
||||
|
||||
if total_trades < 5:
|
||||
trade_bonus *= 0.3 # Scalping needs more trades
|
||||
|
||||
score = sharpe * dd_penalty * trade_bonus * wr_bonus
|
||||
# NEW FORMULA: Profit is the PRIMARY driver, everything else modulates it
|
||||
score = return_score * sharpe_bonus * dd_penalty * trade_bonus * wr_bonus
|
||||
fitness_scores.append(score)
|
||||
|
||||
if not fitness_scores:
|
||||
@@ -236,15 +250,22 @@ class GeneticEvolver:
|
||||
max_dd = m.get('max_drawdown', 0)
|
||||
total_trades = m.get('total_trades', 0)
|
||||
win_rate = m.get('win_rate', 0) / 100.0
|
||||
total_return = m.get('total_return', 0) / 100.0 # Convert % to decimal
|
||||
|
||||
# Fitness = profit-weighted Sharpe with safety constraints
|
||||
dd_penalty = max(1 - max_dd / 100, 0)
|
||||
trade_bonus = math.sqrt(max(total_trades, 0))
|
||||
wr_bonus = 1.0 + max(0, win_rate - 0.5) * 0.5
|
||||
|
||||
|
||||
# Weight actual profit heavily (10x multiplier)
|
||||
profit_score = max(0, total_return) * 10
|
||||
|
||||
# Penalize strategies with few trades
|
||||
if total_trades < 5:
|
||||
trade_bonus *= 0.3
|
||||
|
||||
score = sharpe * dd_penalty * trade_bonus * wr_bonus
|
||||
# Combined score: profit is primary, Sharpe/WR/DD are modifiers
|
||||
score = profit_score * (1 + sharpe) * dd_penalty * trade_bonus * wr_bonus
|
||||
fitness_scores.append(score)
|
||||
|
||||
if not fitness_scores:
|
||||
|
||||
@@ -54,6 +54,7 @@ class RLAgent:
|
||||
|
||||
def __init__(self, state_dim: int, action_dim: int = 7, config: Dict = None):
|
||||
config = config or {}
|
||||
self.config = config # CRITICAL FIX: Save config for memory persistence
|
||||
self.state_dim = state_dim
|
||||
self.action_dim = action_dim
|
||||
|
||||
@@ -237,6 +238,20 @@ class RLAgent:
|
||||
if self.training_losses else 0,
|
||||
}
|
||||
store.save_model_checkpoint('rl_agent', epoch, state_bytes, metrics)
|
||||
|
||||
# Also save experience replay memory
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
db_path_str = str(store.db_path)
|
||||
memory_path = db_path_str.replace('.db', '_rl_memory.pkl')
|
||||
try:
|
||||
with open(memory_path, 'wb') as f:
|
||||
# Save memory as list to avoid deque pickle issues
|
||||
pickle.dump(list(self.memory), f)
|
||||
logger.debug(f"RL memory saved ({len(self.memory)} experiences)")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save RL memory: {e}")
|
||||
|
||||
logger.info(f"RL model saved (epoch {epoch}, epsilon={self.epsilon:.4f})")
|
||||
|
||||
def load(self, store) -> bool:
|
||||
@@ -270,6 +285,22 @@ class RLAgent:
|
||||
self.epsilon = metrics.get('epsilon', self.epsilon)
|
||||
self.steps = metrics.get('steps', self.steps)
|
||||
|
||||
# Load experience replay memory
|
||||
import pickle
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
db_path_str = str(store.db_path)
|
||||
memory_path = db_path_str.replace('.db', '_rl_memory.pkl')
|
||||
try:
|
||||
with open(memory_path, 'rb') as f:
|
||||
saved_memory = pickle.load(f)
|
||||
self.memory = deque(saved_memory, maxlen=self.config['memory_size'])
|
||||
logger.info(f"RL memory loaded ({len(self.memory)} experiences)")
|
||||
except FileNotFoundError:
|
||||
logger.debug("No saved RL memory found, starting with empty buffer")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load RL memory: {e}")
|
||||
|
||||
logger.info(f"RL model loaded (epoch {checkpoint['epoch']}, "
|
||||
f"epsilon={self.epsilon:.4f})")
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user