Update: Krystie reporter integration and strategy improvements

This commit is contained in:
2026-03-23 00:21:19 +01:00
parent 62822a8675
commit 7be73159a4
10 changed files with 789 additions and 519 deletions
+18 -15
View File
@@ -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,
+235
View File
@@ -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
View File
@@ -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
}
}
]
+62 -62
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+31
View File
@@ -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