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