diff --git a/config/auto_config.json b/config/auto_config.json
index a639ab6..359752a 100644
--- a/config/auto_config.json
+++ b/config/auto_config.json
@@ -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,
diff --git a/scripts/krystie/enhanced_reporter.py b/scripts/krystie/enhanced_reporter.py
new file mode 100755
index 0000000..acf8d12
--- /dev/null
+++ b/scripts/krystie/enhanced_reporter.py
@@ -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("🐟 BIGGFISH Daily Report")
+ lines.append(f"📅 {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
+ lines.append("")
+
+ # === PORTFOLIO SNAPSHOT ===
+ lines.append(f"💰 {format_currency(equity)} 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"⚠️ Down {abs(progress):.1f}% from start"
+ elif progress >= 100:
+ progress_text = f"🎯 GOAL REACHED!"
+ else:
+ progress_text = f"🎯 {progress:.1f}% to goal ({format_currency(target - equity)} left)"
+ lines.append(progress_text)
+ lines.append("")
+
+ # === TODAY'S ACTIVITY ===
+ lines.append("📋 Today's Trading")
+ 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"📊 Positions: {len(positions)}")
+ 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("🧠 Learning")
+ 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"🏦 Markets: 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()
diff --git a/src/data/biggfish.db-shm b/src/data/biggfish.db-shm
new file mode 100644
index 0000000..fa47783
Binary files /dev/null and b/src/data/biggfish.db-shm differ
diff --git a/src/data/biggfish.db-wal b/src/data/biggfish.db-wal
new file mode 100644
index 0000000..9b40e38
Binary files /dev/null and b/src/data/biggfish.db-wal differ
diff --git a/src/data/krystie-events.json b/src/data/krystie-events.json
index e540d5e..3101f1f 100644
--- a/src/data/krystie-events.json
+++ b/src/data/krystie-events.json
@@ -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
}
}
]
diff --git a/src/data/krystie-status.json b/src/data/krystie-status.json
index 0025a7a..d1a60e1 100644
--- a/src/data/krystie-status.json
+++ b/src/data/krystie-status.json
@@ -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
}
}
\ No newline at end of file
diff --git a/src/data/store.py b/src/data/store.py
index 6302580..0f914a3 100644
--- a/src/data/store.py
+++ b/src/data/store.py
@@ -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):
diff --git a/src/main_auto.py b/src/main_auto.py
index 7d29679..eeee737 100644
--- a/src/main_auto.py
+++ b/src/main_auto.py
@@ -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)
diff --git a/src/ml/genetic.py b/src/ml/genetic.py
index 21eb0b8..2da0206 100644
--- a/src/ml/genetic.py
+++ b/src/ml/genetic.py
@@ -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:
diff --git a/src/ml/rl_agent.py b/src/ml/rl_agent.py
index e541e66..51e915d 100644
--- a/src/ml/rl_agent.py
+++ b/src/ml/rl_agent.py
@@ -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