Add ML dashboard panel, paper trader, time decay model
- /api/ml endpoint: per-model raw vs calibrated, typhoon, spatial features - ML Predictions card in web dashboard (color-coded, sorted by confidence) - Typhoon Probabilities card (T1/T3/T8 now/72h/120h) - PaperTrader: simulated trading with portfolio Kelly, P&L tracking, trade history persistence, auto-resolution after 24h - TimeDecayModel: theta decay for binary options sigma(t) = sigma_0 * (T-t)^beta (beta=0.4 for weather) Fair price convergence from 50%→model_prob as expiry approaches - Paper trader CLI: --track (monitor), --report, --simulate-days Run dashboard: python web_dashboard.py # see ML panel Run paper: python ml/paper_trader.py --simulate-days 30
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
date,bankroll
|
||||||
|
2026-08-11 11:14,1000.0
|
||||||
|
2026-08-11 11:15,1430.571129983699
|
||||||
|
2026-08-11 11:15,1869.9228851208563
|
||||||
|
2026-08-11 11:15,2309.2746402580137
|
||||||
|
2026-08-11 11:15,2593.77145925936
|
||||||
|
2026-08-11 11:15,3033.8015537575993
|
||||||
|
2026-08-11 11:15,3457.8650454555836
|
||||||
|
2026-08-11 11:15,3885.3876428140006
|
||||||
|
2026-08-11 11:15,4385.387642814001
|
||||||
|
2026-08-11 11:15,4885.387642814001
|
||||||
|
2026-08-11 11:15,5385.387642814001
|
||||||
|
2026-08-11 11:15,5885.387642814001
|
||||||
|
2026-08-11 11:15,6057.0007397876425
|
||||||
|
2026-08-11 11:15,6160.7826990978765
|
||||||
|
2026-08-11 11:15,6347.768603660328
|
||||||
|
2026-08-11 11:15,5847.768603660328
|
||||||
|
2026-08-11 11:15,6347.768603660328
|
||||||
|
2026-08-11 11:15,6847.768603660328
|
||||||
|
2026-08-11 11:15,6347.768603660328
|
||||||
|
2026-08-11 11:15,6847.768603660328
|
||||||
|
2026-08-11 11:15,7347.768603660328
|
||||||
|
2026-08-11 11:15,6847.768603660328
|
||||||
|
2026-08-11 11:15,7215.736592572463
|
||||||
|
2026-08-11 11:15,7715.736592572463
|
||||||
|
2026-08-11 11:15,8215.736592572463
|
||||||
|
2026-08-11 11:15,8715.736592572463
|
||||||
|
2026-08-11 11:15,9215.736592572463
|
||||||
|
2026-08-11 11:15,9715.736592572463
|
||||||
|
2026-08-11 11:15,10215.736592572463
|
||||||
|
2026-08-11 11:15,10715.736592572463
|
||||||
|
2026-08-11 11:15,10832.307490599274
|
||||||
|
2026-08-11 11:15,11332.307490599274
|
||||||
|
2026-08-11 11:15,11832.307490599274
|
||||||
|
2026-08-11 11:15,12332.307490599274
|
||||||
|
2026-08-11 11:15,12832.307490599274
|
||||||
|
2026-08-11 11:15,13332.307490599274
|
||||||
|
2026-08-11 11:15,13832.307490599274
|
||||||
|
2026-08-11 11:15,14332.307490599274
|
||||||
|
2026-08-11 11:15,14441.807689556865
|
||||||
|
2026-08-11 11:15,13941.807689556865
|
||||||
|
2026-08-11 11:15,14441.807689556865
|
||||||
|
2026-08-11 11:15,14941.807689556865
|
||||||
|
2026-08-11 11:15,15441.807689556865
|
||||||
|
2026-08-11 11:15,15941.807689556865
|
||||||
|
2026-08-11 11:15,16441.807689556867
|
||||||
|
2026-08-11 11:15,16941.807689556867
|
||||||
|
2026-08-11 11:15,16441.807689556867
|
||||||
|
2026-08-11 11:15,16729.018474036096
|
||||||
|
2026-08-11 11:15,17229.018474036096
|
||||||
|
2026-08-11 11:15,17729.018474036096
|
||||||
|
2026-08-11 11:15,18229.018474036096
|
||||||
|
2026-08-11 11:15,18729.018474036096
|
||||||
|
2026-08-11 11:15,19229.018474036096
|
||||||
|
2026-08-11 11:15,19729.018474036096
|
||||||
|
2026-08-11 11:15,20229.018474036096
|
||||||
|
2026-08-11 11:15,20356.41021319305
|
||||||
|
2026-08-11 11:15,20734.229569661333
|
||||||
|
2026-08-11 11:15,21234.229569661333
|
||||||
|
2026-08-11 11:15,21734.229569661333
|
||||||
|
2026-08-11 11:15,22234.229569661333
|
||||||
|
2026-08-11 11:15,22734.229569661333
|
||||||
|
2026-08-11 11:15,23234.229569661333
|
||||||
|
2026-08-11 11:15,23734.229569661333
|
||||||
|
2026-08-11 11:15,24234.229569661333
|
||||||
|
2026-08-11 11:15,24371.707348301075
|
||||||
|
2026-08-11 11:15,24871.707348301075
|
||||||
|
2026-08-11 11:15,25371.707348301075
|
||||||
|
2026-08-11 11:15,25871.707348301075
|
||||||
|
2026-08-11 11:15,26371.707348301075
|
||||||
|
2026-08-11 11:15,26871.707348301075
|
||||||
|
2026-08-11 11:15,27371.707348301075
|
||||||
|
2026-08-11 11:15,27871.707348301075
|
||||||
|
2026-08-11 11:15,27971.49601527572
|
||||||
|
2026-08-11 11:15,28345.77538017773
|
||||||
|
2026-08-11 11:15,28845.77538017773
|
||||||
|
2026-08-11 11:15,29345.77538017773
|
||||||
|
2026-08-11 11:15,29845.77538017773
|
||||||
|
@@ -0,0 +1,160 @@
|
|||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368459", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 430.571129983699, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368452", "entry_roll": 1000.0}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368462", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 439.3517551371573, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368461", "entry_roll": 1000.0}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368464", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 439.3517551371573, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368463", "entry_roll": 1000.0}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368466", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 284.49681900134635, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368465", "entry_roll": 1000.0}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368469", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 440.03009449823935, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368468", "entry_roll": 1000.0}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368471", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 424.06349169798426, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368470", "entry_roll": 1000.0}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.368473", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 427.5225973584169, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.368472", "entry_roll": 1000.0}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408326", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 430.571129983699, "bankroll_after": 1430.571129983699, "hours_open": 1.1071388888888888e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408336", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 439.3517551371573, "bankroll_after": 1869.9228851208563, "hours_open": 1.1075000000000002e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408342", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 439.3517551371573, "bankroll_after": 2309.2746402580137, "hours_open": 1.1076388888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408347", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 284.49681900134635, "bankroll_after": 2593.77145925936, "hours_open": 1.107722222222222e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408351", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 440.03009449823935, "bankroll_after": 3033.8015537575993, "hours_open": 1.1077777777777778e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408356", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 424.06349169798426, "bankroll_after": 3457.8650454555836, "hours_open": 1.1078611111111112e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.408360", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 427.5225973584169, "bankroll_after": 3885.3876428140006, "hours_open": 1.1079166666666665e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408463", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408461", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408465", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408464", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408468", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408467", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408474", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408469", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408476", "target": "rain_gt_0mm_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 74.44762501700039, "entry_time": "2026-08-11T11:15:25.408475", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408478", "target": "temp_gt_30c_24h", "side": "buy_yes", "size_usdc": 468.7101215358844, "entry_prob": 93.93535125441778, "entry_market": 81.8718960868684, "entry_time": "2026-08-11T11:15:25.408477", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408480", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 400.12544007919644, "entry_prob": 78.44979569886223, "entry_market": 68.15154292809973, "entry_time": "2026-08-11T11:15:25.408479", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408482", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408481", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408484", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408483", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.408486", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.408485", "entry_roll": 3885.3876428140006}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445074", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 4385.387642814001, "hours_open": 1.0166111111111111e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445084", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 4885.387642814001, "hours_open": 1.0171111111111112e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445089", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 5385.387642814001, "hours_open": 1.0171944444444444e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445094", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 5885.387642814001, "hours_open": 1.0172777777777779e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445098", "target": "rain_gt_0mm_24h", "won": true, "profit_usdc": 171.6130969736417, "bankroll_after": 6057.0007397876425, "hours_open": 1.0172222222222222e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445103", "target": "temp_gt_30c_24h", "won": true, "profit_usdc": 103.78195931023365, "bankroll_after": 6160.7826990978765, "hours_open": 1.0173055555555557e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445107", "target": "temp_gt_35c_24h", "won": true, "profit_usdc": 186.9859045624518, "bankroll_after": 6347.768603660328, "hours_open": 1.0173611111111111e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445112", "target": "rain_gt_0mm_24h_2026-08-13", "won": false, "profit_usdc": -500.0, "bankroll_after": 5847.768603660328, "hours_open": 1.0174444444444444e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445116", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 6347.768603660328, "hours_open": 1.0175e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.445121", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 6847.768603660328, "hours_open": 1.0175833333333333e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445215", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445213", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445217", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445216", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445219", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445218", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445221", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445221", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445224", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 57.60581108834134, "entry_time": "2026-08-11T11:15:25.445223", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445226", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445225", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445228", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445227", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.445230", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.445229", "entry_roll": 6847.768603660328}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489621", "target": "temp_gt_30c_24h_2026-08-11", "won": false, "profit_usdc": -500.0, "bankroll_after": 6347.768603660328, "hours_open": 1.2330277777777777e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489633", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 6847.768603660328, "hours_open": 1.2336388888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489640", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 7347.768603660328, "hours_open": 1.2338333333333334e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489646", "target": "temp_gt_35c_24h_2026-08-12", "won": false, "profit_usdc": -500.0, "bankroll_after": 6847.768603660328, "hours_open": 1.2339166666666667e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489653", "target": "temp_gt_35c_24h", "won": true, "profit_usdc": 367.967988912135, "bankroll_after": 7215.736592572463, "hours_open": 1.2340555555555556e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489659", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 7715.736592572463, "hours_open": 1.2341666666666666e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489666", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 8215.736592572463, "hours_open": 1.2342777777777779e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.489673", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 8715.736592572463, "hours_open": 1.2344166666666666e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489770", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489768", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489773", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489772", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489776", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489775", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489779", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489778", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489782", "target": "rain_gt_0mm_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 81.09367496911236, "entry_time": "2026-08-11T11:15:25.489780", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489784", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489783", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489787", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489786", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.489790", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.489788", "entry_roll": 8715.736592572463}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527189", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 9215.736592572463, "hours_open": 1.0390833333333334e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527197", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 9715.736592572463, "hours_open": 1.0395e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527202", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 10215.736592572463, "hours_open": 1.0395555555555555e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527205", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 10715.736592572463, "hours_open": 1.0395833333333333e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527209", "target": "rain_gt_0mm_24h", "won": true, "profit_usdc": 116.57089802681182, "bankroll_after": 10832.307490599274, "hours_open": 1.0396388888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527213", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 11332.307490599274, "hours_open": 1.0396388888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527217", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 11832.307490599274, "hours_open": 1.0396666666666668e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.527220", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 12332.307490599274, "hours_open": 1.0397222222222222e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527313", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527311", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527315", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527314", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527318", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527317", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527320", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527319", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527322", "target": "rain_gt_0mm_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 82.03442769258075, "entry_time": "2026-08-11T11:15:25.527321", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527324", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 67.99128161110626, "entry_time": "2026-08-11T11:15:25.527323", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527326", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527325", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527328", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527327", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.527330", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.527329", "entry_roll": 12332.307490599274}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564433", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 12832.307490599274, "hours_open": 1.0307777777777779e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564442", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 13332.307490599274, "hours_open": 1.03125e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564447", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 13832.307490599274, "hours_open": 1.0313055555555556e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564451", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 14332.307490599274, "hours_open": 1.0313611111111111e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564454", "target": "rain_gt_0mm_24h", "won": true, "profit_usdc": 109.50019895759004, "bankroll_after": 14441.807689556865, "hours_open": 1.0314166666666665e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564458", "target": "temp_gt_35c_24h", "won": false, "profit_usdc": -500.0, "bankroll_after": 13941.807689556865, "hours_open": 1.0314722222222223e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564462", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 14441.807689556865, "hours_open": 1.0315e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564465", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 14941.807689556865, "hours_open": 1.0315555555555556e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.564469", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 15441.807689556865, "hours_open": 1.0315833333333334e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564562", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564560", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564566", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564564", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564568", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564567", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564571", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564570", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564574", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 63.51539001473006, "entry_time": "2026-08-11T11:15:25.564572", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564576", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564575", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564579", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564578", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.564581", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.564580", "entry_roll": 15441.807689556865}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603651", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 15941.807689556865, "hours_open": 1.0854166666666667e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603661", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 16441.807689556867, "hours_open": 1.0859166666666668e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603665", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 16941.807689556867, "hours_open": 1.0859722222222222e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603668", "target": "temp_gt_35c_24h_2026-08-12", "won": false, "profit_usdc": -500.0, "bankroll_after": 16441.807689556867, "hours_open": 1.086e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603672", "target": "temp_gt_35c_24h", "won": true, "profit_usdc": 287.2107844792316, "bankroll_after": 16729.018474036096, "hours_open": 1.0860555555555555e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603676", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 17229.018474036096, "hours_open": 1.0860555555555555e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603679", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 17729.018474036096, "hours_open": 1.0860833333333335e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.603682", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 18229.018474036096, "hours_open": 1.0861111111111112e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603765", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603764", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603768", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603767", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603770", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603769", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603773", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603772", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603775", "target": "temp_gt_30c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 79.69502446300415, "entry_time": "2026-08-11T11:15:25.603774", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603777", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 56.959327259727026, "entry_time": "2026-08-11T11:15:25.603776", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603783", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603778", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603785", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603784", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.603787", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.603786", "entry_roll": 18229.018474036096}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645463", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 18729.018474036096, "hours_open": 1.1578055555555556e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645472", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 19229.018474036096, "hours_open": 1.1583611111111111e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645476", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 19729.018474036096, "hours_open": 1.1584722222222222e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645480", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 20229.018474036096, "hours_open": 1.1585e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645484", "target": "temp_gt_30c_24h", "won": true, "profit_usdc": 127.3917391569519, "bankroll_after": 20356.41021319305, "hours_open": 1.1585277777777778e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645488", "target": "temp_gt_35c_24h", "won": true, "profit_usdc": 377.8193564682847, "bankroll_after": 20734.229569661333, "hours_open": 1.1585833333333334e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645491", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 21234.229569661333, "hours_open": 1.158638888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645495", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 21734.229569661333, "hours_open": 1.1585833333333334e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.645498", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 22234.229569661333, "hours_open": 1.158611111111111e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645594", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645592", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645598", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645596", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645601", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645600", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645603", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645602", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645606", "target": "temp_gt_30c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 78.43410652947102, "entry_time": "2026-08-11T11:15:25.645605", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645609", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645607", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645617", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645615", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.645619", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.645618", "entry_roll": 22234.229569661333}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684377", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 22734.229569661333, "hours_open": 1.077e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684386", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 23234.229569661333, "hours_open": 1.077388888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684390", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 23734.229569661333, "hours_open": 1.0774444444444445e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684394", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 24234.229569661333, "hours_open": 1.0774722222222222e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684398", "target": "temp_gt_30c_24h", "won": true, "profit_usdc": 137.47777863974105, "bankroll_after": 24371.707348301075, "hours_open": 1.0775e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684402", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 24871.707348301075, "hours_open": 1.0775555555555555e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684405", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 25371.707348301075, "hours_open": 1.0774444444444445e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.684409", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 25871.707348301075, "hours_open": 1.0774444444444445e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684490", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684488", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684492", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684491", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684495", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684494", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684497", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684496", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684499", "target": "temp_gt_30c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 83.36269548439769, "entry_time": "2026-08-11T11:15:25.684498", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684501", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 57.18995781812152, "entry_time": "2026-08-11T11:15:25.684500", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684503", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684502", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684505", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684504", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.684507", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.684506", "entry_roll": 25871.707348301075}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721058", "target": "temp_gt_30c_24h_2026-08-11", "won": true, "profit_usdc": 500.0, "bankroll_after": 26371.707348301075, "hours_open": 1.015388888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721067", "target": "rain_gt_0mm_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 26871.707348301075, "hours_open": 1.015888888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721073", "target": "temp_gt_30c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 27371.707348301075, "hours_open": 1.0159722222222223e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721077", "target": "temp_gt_35c_24h_2026-08-12", "won": true, "profit_usdc": 500.0, "bankroll_after": 27871.707348301075, "hours_open": 1.0160555555555555e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721082", "target": "temp_gt_30c_24h", "won": true, "profit_usdc": 99.78866697464323, "bankroll_after": 27971.49601527572, "hours_open": 1.016138888888889e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721087", "target": "temp_gt_35c_24h", "won": true, "profit_usdc": 374.27936490200966, "bankroll_after": 28345.77538017773, "hours_open": 1.0162222222222222e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721092", "target": "rain_gt_0mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 28845.77538017773, "hours_open": 1.0162777777777778e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721095", "target": "rain_gt_5mm_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 29345.77538017773, "hours_open": 1.0163611111111113e-05}
|
||||||
|
{"action": "close", "time": "2026-08-11T11:15:25.721099", "target": "temp_gt_30c_24h_2026-08-13", "won": true, "profit_usdc": 500.0, "bankroll_after": 29845.77538017773, "hours_open": 1.0164166666666667e-05}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721180", "target": "temp_gt_30c_24h_2026-08-11", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.05728522682189, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721178", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721183", "target": "rain_gt_0mm_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721181", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721185", "target": "temp_gt_30c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 93.93535125441778, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721184", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721187", "target": "temp_gt_35c_24h_2026-08-12", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721186", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721189", "target": "temp_gt_35c_24h", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 78.44979569886223, "entry_market": 67.49202483107283, "entry_time": "2026-08-11T11:15:25.721188", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721191", "target": "rain_gt_0mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 94.00318546186173, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721190", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721194", "target": "rain_gt_5mm_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.4065187951951, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721193", "entry_roll": 29845.77538017773}
|
||||||
|
{"action": "open", "time": "2026-08-11T11:15:25.721196", "target": "temp_gt_30c_24h_2026-08-13", "side": "buy_yes", "size_usdc": 500.0, "entry_prob": 92.75243074488063, "entry_market": 50.0, "entry_time": "2026-08-11T11:15:25.721195", "entry_roll": 29845.77538017773}
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Paper Trading Simulator for HK Weather Prediction Markets.
|
||||||
|
|
||||||
|
Simulates trading against hypothetical market prices using ML model
|
||||||
|
predictions, tracking P&L, Sharpe ratio, and drawdown over time.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python ml/paper_trader.py # Single run
|
||||||
|
python ml/paper_trader.py --track # Monitor mode (every 6h)
|
||||||
|
python ml/paper_trader.py --report # Print historical report
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from ml.predictor import MLPredictor
|
||||||
|
from ml.model import TARGET_DEFINITIONS
|
||||||
|
from strategy.portfolio_kelly import PortfolioKelly
|
||||||
|
|
||||||
|
TRADE_LOG = Path(__file__).parent.parent / "data" / "paper_trades.jsonl"
|
||||||
|
HISTORY_LOG = Path(__file__).parent.parent / "data" / "paper_pnl.csv"
|
||||||
|
|
||||||
|
|
||||||
|
class TimeDecayModel:
|
||||||
|
"""Models theta decay for binary options approaching resolution.
|
||||||
|
|
||||||
|
Near-expiry markets exhibit predictable uncertainty collapse.
|
||||||
|
The market often overprices uncertainty at intermediate horizons
|
||||||
|
and suddenly converges to certainty near expiry.
|
||||||
|
|
||||||
|
Model: sigma(t) = sigma_0 * (T - t)^beta
|
||||||
|
Where beta ~ 0.3-0.5 for weather outcomes (slower decay than financial).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.beta = 0.4 # Weather-specific decay exponent
|
||||||
|
self.min_sigma = 0.02 # Minimum uncertainty at t=0
|
||||||
|
|
||||||
|
def decay_factor(self, hours_to_expiry: float, max_horizon: float = 168.0) -> float:
|
||||||
|
"""
|
||||||
|
Compute time decay factor for binary option.
|
||||||
|
|
||||||
|
At t=max_horizon: factor = 1.0 (maximum uncertainty)
|
||||||
|
At t=0: factor = min_sigma/sigma_0 (minimum uncertainty)
|
||||||
|
|
||||||
|
Returns factor in [0, 1] representing remaining uncertainty fraction.
|
||||||
|
"""
|
||||||
|
if hours_to_expiry <= 0:
|
||||||
|
return self.min_sigma
|
||||||
|
if hours_to_expiry >= max_horizon:
|
||||||
|
return 1.0
|
||||||
|
tau = hours_to_expiry / max_horizon
|
||||||
|
return self.min_sigma + (1.0 - self.min_sigma) * tau ** self.beta
|
||||||
|
|
||||||
|
def fair_price_convergence(
|
||||||
|
self,
|
||||||
|
model_probability: float,
|
||||||
|
hours_to_expiry: float,
|
||||||
|
market_probability: Optional[float] = None,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Compute fair price adjusted for time decay.
|
||||||
|
|
||||||
|
When hours_to_expiry is large, the model probability should be closer
|
||||||
|
to 50% (maximum uncertainty). As expiry approaches, it should converge
|
||||||
|
to either 0% or 100%.
|
||||||
|
|
||||||
|
Returns dict with fair_price, uncertainty_band, and edge vs market.
|
||||||
|
"""
|
||||||
|
decay = self.decay_factor(hours_to_expiry)
|
||||||
|
prob_0_1 = model_probability / 100.0
|
||||||
|
|
||||||
|
# Fair price: blend between 50% (at t=far) and model_prob (at t=0)
|
||||||
|
fair_price = 50.0 + (model_probability - 50.0) * (1.0 - decay)
|
||||||
|
|
||||||
|
# Uncertainty band: width proportional to remaining time
|
||||||
|
# At t=0: band = 0 (certain). At t=max: band = 30pp.
|
||||||
|
if market_probability is not None:
|
||||||
|
edge = fair_price - market_probability
|
||||||
|
else:
|
||||||
|
edge = 0.0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"fair_price": fair_price,
|
||||||
|
"time_decay_factor": 1.0 - decay,
|
||||||
|
"edge_vs_market": edge,
|
||||||
|
"hours_to_expiry": hours_to_expiry,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PaperTrader:
|
||||||
|
"""Simulate trading using ML predictions and track P&L."""
|
||||||
|
|
||||||
|
def __init__(self, bankroll: float = 1000.0, min_edge_bps: float = 50):
|
||||||
|
self.bankroll = bankroll
|
||||||
|
self.initial_bankroll = bankroll
|
||||||
|
self.min_edge_bps = min_edge_bps
|
||||||
|
self.predictor = MLPredictor(bankroll_usdc=bankroll, min_edge_bps=min_edge_bps)
|
||||||
|
self.time_decay = TimeDecayModel()
|
||||||
|
self.portfolio_kelly = PortfolioKelly()
|
||||||
|
|
||||||
|
# State
|
||||||
|
self.positions: Dict[str, dict] = {} # target -> {side, size, entry_prob, entry_time}
|
||||||
|
self.trade_history: List[dict] = []
|
||||||
|
self.pnl_history: List[float] = [bankroll]
|
||||||
|
self.dates: List[str] = [datetime.now().strftime("%Y-%m-%d %H:%M")]
|
||||||
|
|
||||||
|
self._load_history()
|
||||||
|
|
||||||
|
def run(self, market_prices: Optional[Dict[str, float]] = None, resolution_hours: float = 24.0):
|
||||||
|
"""Execute a single trading cycle.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
market_prices : dict
|
||||||
|
{target_name: market_implied_probability_0_100}
|
||||||
|
If None, uses simulated prices (model - noise).
|
||||||
|
resolution_hours : float
|
||||||
|
Hours until positions auto-resolve (24h default, 0 for immediate).
|
||||||
|
"""
|
||||||
|
self.predictor.fetch_and_predict()
|
||||||
|
|
||||||
|
if market_prices is None:
|
||||||
|
market_prices = self._simulate_market_prices()
|
||||||
|
|
||||||
|
self._close_resolved(resolution_hours)
|
||||||
|
self._open_new(market_prices)
|
||||||
|
self._save()
|
||||||
|
|
||||||
|
def _simulate_market_prices(self) -> Dict[str, float]:
|
||||||
|
"""Simulate market prices with realistic bid-ask spreads."""
|
||||||
|
prices = {}
|
||||||
|
predictions = self.predictor._last_predictions or {}
|
||||||
|
|
||||||
|
for target in TARGET_DEFINITIONS:
|
||||||
|
model_p = predictions.get(target, 50.0)
|
||||||
|
# Market price: model + noise + spread
|
||||||
|
noise = np.random.normal(0, 8) # 8% stdev market noise
|
||||||
|
# Systematic bias: market underweights extremes
|
||||||
|
bias = -0.15 * (model_p - 50)
|
||||||
|
market_p = model_p + noise + bias
|
||||||
|
# Random spread: 0.5-3%
|
||||||
|
spread = np.random.uniform(0.5, 3.0)
|
||||||
|
# Round to nearest spread tick
|
||||||
|
market_p = round(market_p / spread) * spread
|
||||||
|
prices[target] = float(np.clip(market_p, 1, 99))
|
||||||
|
|
||||||
|
return prices
|
||||||
|
|
||||||
|
def _open_new(self, market_prices: Dict[str, float]):
|
||||||
|
"""Open new positions based on ML signals."""
|
||||||
|
predictions = self.predictor._last_predictions or {}
|
||||||
|
|
||||||
|
# Collect edges
|
||||||
|
edges = {}
|
||||||
|
for target, model_p in predictions.items():
|
||||||
|
mkt_p = market_prices.get(target, 50.0)
|
||||||
|
edge_decimal = (model_p - mkt_p) / 100.0
|
||||||
|
if abs(edge_decimal * 100) >= self.min_edge_bps:
|
||||||
|
edges[target] = edge_decimal
|
||||||
|
|
||||||
|
if not edges:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Portfolio Kelly sizing
|
||||||
|
sizes = self.portfolio_kelly.simultaneous_kelly(edges, bankroll=self.bankroll)
|
||||||
|
|
||||||
|
for target, size in sizes.items():
|
||||||
|
if size < 1.0 or target in self.positions:
|
||||||
|
continue
|
||||||
|
|
||||||
|
model_p = predictions[target]
|
||||||
|
mkt_p = market_prices.get(target, 50.0)
|
||||||
|
side = "buy_yes" if model_p > mkt_p else "buy_no"
|
||||||
|
|
||||||
|
position = {
|
||||||
|
"target": target,
|
||||||
|
"side": side,
|
||||||
|
"size_usdc": size,
|
||||||
|
"entry_prob": model_p,
|
||||||
|
"entry_market": mkt_p,
|
||||||
|
"entry_time": datetime.now().isoformat(),
|
||||||
|
"entry_roll": self.bankroll,
|
||||||
|
}
|
||||||
|
self.positions[target] = position
|
||||||
|
|
||||||
|
self.trade_history.append({
|
||||||
|
"action": "open",
|
||||||
|
"time": datetime.now().isoformat(),
|
||||||
|
**position,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _close_resolved(self, resolution_hours: float = 24.0):
|
||||||
|
"""Close positions where outcomes are known."""
|
||||||
|
closed = []
|
||||||
|
for target, pos in list(self.positions.items()):
|
||||||
|
# Simulate outcome resolution after 24h
|
||||||
|
entry_time = datetime.fromisoformat(pos["entry_time"])
|
||||||
|
hours_open = (datetime.now() - entry_time).total_seconds() / 3600
|
||||||
|
|
||||||
|
if hours_open >= resolution_hours:
|
||||||
|
# Random resolution biased by our probability
|
||||||
|
our_p = pos["entry_prob"] / 100.0
|
||||||
|
won = np.random.random() < our_p
|
||||||
|
|
||||||
|
if pos["side"] == "buy_yes":
|
||||||
|
profit = pos["size_usdc"] * ((1 - pos["entry_market"] / 100) / (pos["entry_market"] / 100)) if won else -pos["size_usdc"]
|
||||||
|
else:
|
||||||
|
mkt_no = 100 - pos["entry_market"]
|
||||||
|
profit = pos["size_usdc"] * ((1 - mkt_no / 100) / (mkt_no / 100)) if won else -pos["size_usdc"]
|
||||||
|
|
||||||
|
self.bankroll += profit
|
||||||
|
self.pnl_history.append(self.bankroll)
|
||||||
|
self.dates.append(datetime.now().strftime("%Y-%m-%d %H:%M"))
|
||||||
|
|
||||||
|
self.trade_history.append({
|
||||||
|
"action": "close",
|
||||||
|
"time": datetime.now().isoformat(),
|
||||||
|
"target": target,
|
||||||
|
"won": won,
|
||||||
|
"profit_usdc": profit,
|
||||||
|
"bankroll_after": self.bankroll,
|
||||||
|
"hours_open": hours_open,
|
||||||
|
})
|
||||||
|
closed.append(target)
|
||||||
|
|
||||||
|
for target in closed:
|
||||||
|
del self.positions[target]
|
||||||
|
|
||||||
|
def report(self) -> str:
|
||||||
|
"""Generate performance report."""
|
||||||
|
if len(self.pnl_history) < 2:
|
||||||
|
return "No trading history yet."
|
||||||
|
|
||||||
|
pnl = np.array(self.pnl_history)
|
||||||
|
returns = np.diff(pnl) / (pnl[:-1] + 1e-9)
|
||||||
|
|
||||||
|
total_trades = len([t for t in self.trade_history if t["action"] == "close"])
|
||||||
|
wins = len([t for t in self.trade_history if t["action"] == "close" and t.get("won")])
|
||||||
|
losses = total_trades - wins
|
||||||
|
|
||||||
|
sharpe = np.mean(returns) / max(np.std(returns), 1e-9) * np.sqrt(365) if len(returns) > 1 else 0
|
||||||
|
max_dd = max(1 - np.minimum.accumulate(pnl) / np.maximum.accumulate(pnl)) * 100
|
||||||
|
roi = (self.bankroll - self.initial_bankroll) / self.initial_bankroll * 100
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"=== Paper Trading Report ({datetime.now():%Y-%m-%d %H:%M}) ===",
|
||||||
|
f" Bankroll: ${self.initial_bankroll:.0f} → ${self.bankroll:.0f} ({roi:+.1f}%)",
|
||||||
|
f" Trades: {total_trades} ({wins}W/{losses}L, {wins/max(total_trades,1)*100:.0f}% win)",
|
||||||
|
f" Sharpe: {sharpe:.2f}",
|
||||||
|
f" Max DD: {max_dd:.1f}%",
|
||||||
|
f" Open positions: {len(self.positions)}",
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.positions:
|
||||||
|
lines.append(f" Open:")
|
||||||
|
for target, pos in self.positions.items():
|
||||||
|
lines.append(f" {target}: {pos['side']} ${pos['size_usdc']:.0f} @ {pos['entry_prob']:.0f}%")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
"""Persist trade state."""
|
||||||
|
TRADE_LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(TRADE_LOG, "w") as f:
|
||||||
|
for t in self.trade_history:
|
||||||
|
f.write(json.dumps(t) + "\n")
|
||||||
|
|
||||||
|
pd.DataFrame({
|
||||||
|
"date": self.dates,
|
||||||
|
"bankroll": self.pnl_history,
|
||||||
|
}).to_csv(HISTORY_LOG, index=False)
|
||||||
|
|
||||||
|
def _load_history(self):
|
||||||
|
"""Load previous trading history."""
|
||||||
|
if HISTORY_LOG.exists():
|
||||||
|
try:
|
||||||
|
df = pd.read_csv(HISTORY_LOG)
|
||||||
|
self.pnl_history = df["bankroll"].tolist()
|
||||||
|
self.dates = df["date"].tolist()
|
||||||
|
self.bankroll = self.pnl_history[-1] if self.pnl_history else self.initial_bankroll
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if TRADE_LOG.exists():
|
||||||
|
try:
|
||||||
|
with open(TRADE_LOG) as f:
|
||||||
|
for line in f:
|
||||||
|
if line.strip():
|
||||||
|
self.trade_history.append(json.loads(line))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Paper trading simulator")
|
||||||
|
parser.add_argument("--bankroll", type=float, default=1000.0)
|
||||||
|
parser.add_argument("--edge", type=float, default=50, help="Min edge in bps")
|
||||||
|
parser.add_argument("--track", action="store_true", help="Run continuously")
|
||||||
|
parser.add_argument("--report", action="store_true", help="Print report and exit")
|
||||||
|
parser.add_argument("--simulate-days", type=int, default=0, help="Simulate N days of trading")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
trader = PaperTrader(bankroll=args.bankroll, min_edge_bps=args.edge)
|
||||||
|
|
||||||
|
if args.report:
|
||||||
|
print(trader.report())
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.simulate_days > 0:
|
||||||
|
print(f"Simulating {args.simulate_days} days of trading...")
|
||||||
|
for i in range(args.simulate_days):
|
||||||
|
trader.run()
|
||||||
|
if (i + 1) % 10 == 0:
|
||||||
|
print(f" Day {i+1}/{args.simulate_days} | Bankroll: ${trader.bankroll:.0f}")
|
||||||
|
print(trader.report())
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.track:
|
||||||
|
print(f"Paper trading monitor starting. Bankroll: ${args.bankroll:.0f}")
|
||||||
|
print("Running every 6 hours. Ctrl+C to stop.\n")
|
||||||
|
|
||||||
|
trader.run()
|
||||||
|
print(trader.report())
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
time.sleep(6 * 3600)
|
||||||
|
trader.run()
|
||||||
|
print(trader.report())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nStopping monitor.")
|
||||||
|
print(trader.report())
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
trader.run()
|
||||||
|
print(trader.report())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+141
-4
@@ -26,6 +26,15 @@ from weather.openmeteo_client import OpenMeteoClient
|
|||||||
from strategy.kelly import KellyCriterion
|
from strategy.kelly import KellyCriterion
|
||||||
from config import HK_COORDS
|
from config import HK_COORDS
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
try:
|
||||||
|
from ml import MLPredictor
|
||||||
|
_ml_available = True
|
||||||
|
except Exception:
|
||||||
|
_ml_available = False
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
HTML_TEMPLATE = r'''
|
HTML_TEMPLATE = r'''
|
||||||
@@ -193,6 +202,19 @@ HTML_TEMPLATE = r'''
|
|||||||
<div id="comparison-table"></div>
|
<div id="comparison-table"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ML PREDICTIONS -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>ML Model Predictions <span class="model-tag">Logistic Regression</span></h3>
|
||||||
|
<div id="ml-loading" class="loading">Loading...</div>
|
||||||
|
<div id="ml-content" style="display:none"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TYPHOON -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>Typhoon Probabilities <span class="model-tag">Climatological</span></h3>
|
||||||
|
<div id="typhoon-content" class="stat-sub">Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- TRADING SIGNALS -->
|
<!-- TRADING SIGNALS -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Trading Signals</h3>
|
<h3>Trading Signals</h3>
|
||||||
@@ -240,10 +262,25 @@ function renderAll(data) {
|
|||||||
renderTomorrow(data.tomorrow);
|
renderTomorrow(data.tomorrow);
|
||||||
renderCharts(data.forecast);
|
renderCharts(data.forecast);
|
||||||
renderComparison(data.forecast);
|
renderComparison(data.forecast);
|
||||||
renderSignals(data.signals);
|
renderSignals(data.signals);
|
||||||
renderKelly(data.kelly);
|
renderKelly(data.kelly);
|
||||||
renderRaw(data.forecast);
|
renderRaw(data.forecast);
|
||||||
renderTyphoon(data.typhoon);
|
renderTyphoon(data.typhoon);
|
||||||
|
}
|
||||||
|
// ML data refreshes independently (slower)
|
||||||
|
fetchML();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchML() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/ml');
|
||||||
|
const data = await resp.json();
|
||||||
|
renderML(data);
|
||||||
|
renderTyphoonML(data.typhoon);
|
||||||
|
} catch(e) {
|
||||||
|
document.getElementById('ml-loading').style.display = 'block';
|
||||||
|
document.getElementById('ml-content').style.display = 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCurrent(current, typhoon) {
|
function renderCurrent(current, typhoon) {
|
||||||
@@ -467,6 +504,59 @@ function renderRaw(fc) {
|
|||||||
div.innerHTML = '<pre style="font-size:11px;color:#8b949e;overflow-x:auto">' + JSON.stringify(fc, null, 2) + '</pre>';
|
div.innerHTML = '<pre style="font-size:11px;color:#8b949e;overflow-x:auto">' + JSON.stringify(fc, null, 2) + '</pre>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderML(data) {
|
||||||
|
document.getElementById('ml-loading').style.display = 'none';
|
||||||
|
const div = document.getElementById('ml-content');
|
||||||
|
div.style.display = '';
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
div.innerHTML = '<div class="stat-sub">ML models not available (train first: python ml/train.py)</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const models = data.models || {};
|
||||||
|
if (Object.keys(models).length === 0) {
|
||||||
|
div.innerHTML = '<div class="stat-sub">No model predictions yet</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
const sorted = Object.entries(models).sort((a, b) => {
|
||||||
|
const x = Math.abs(a[1].calibrated - 50);
|
||||||
|
const y = Math.abs(b[1].calibrated - 50);
|
||||||
|
return y - x;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [name, m] of sorted) {
|
||||||
|
const cls = m.calibrated > 75 ? 'badge-green' : (m.calibrated < 25 ? 'badge-danger' : 'badge-info');
|
||||||
|
html += `<div class="signal-row">
|
||||||
|
<div>${m.description} <span class="badge ${cls}">${m.calibrated.toFixed(1)}%</span></div>
|
||||||
|
<div style="font-size:10px;color:#484f58">raw=${m.raw.toFixed(1)}% · ${m.method}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
div.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTyphoonML(typhoon) {
|
||||||
|
const div = document.getElementById('typhoon-content');
|
||||||
|
if (!typhoon || Object.keys(typhoon).length === 0) {
|
||||||
|
div.innerHTML = '<span class="stat-sub">No data</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let html = '';
|
||||||
|
const levels = ['typhoon_T1', 'typhoon_T3', 'typhoon_T8'];
|
||||||
|
for (const k of levels) {
|
||||||
|
if (typhoon[k] !== undefined) {
|
||||||
|
const cls = typhoon[k] > 30 ? 'badge-warn' : (typhoon[k] > 10 ? 'badge-info' : '');
|
||||||
|
html += `<span class="badge ${cls || ''}" style="margin:2px">${k.replace('typhoon_','')}: ${typhoon[k].toFixed(1)}%</span> `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typhoon['typhoon_T8_72h'] !== undefined) {
|
||||||
|
html += `<div style="margin-top:4px;font-size:11px">T8/72h: ${typhoon['typhoon_T8_72h'].toFixed(1)}% · T8/120h: ${(typhoon['typhoon_T8_120h']||0).toFixed(1)}%</div>`;
|
||||||
|
}
|
||||||
|
div.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
setInterval(refresh, 300000); // Every 5 min
|
setInterval(refresh, 300000); // Every 5 min
|
||||||
</script>
|
</script>
|
||||||
@@ -629,6 +719,53 @@ def api_dashboard():
|
|||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/ml")
|
||||||
|
def api_ml():
|
||||||
|
"""Return ML model predictions."""
|
||||||
|
if not _ml_available:
|
||||||
|
return jsonify({"error": "ML not available"}), 503
|
||||||
|
|
||||||
|
try:
|
||||||
|
predictor = MLPredictor()
|
||||||
|
predictor.fetch_and_predict()
|
||||||
|
predictions = predictor._last_predictions or {}
|
||||||
|
|
||||||
|
# Per-model raw vs calibrated
|
||||||
|
models = {}
|
||||||
|
for target, model in predictor.ensemble.models.items():
|
||||||
|
if predictor._last_features is not None:
|
||||||
|
raw = float(model.predict_raw(predictor._last_features[1:2])[0]) if len(predictor._last_features) > 1 else 0.5
|
||||||
|
cal = float(model.predict_proba(predictor._last_features[1:2])[0]) if len(predictor._last_features) > 1 else 50.0
|
||||||
|
else:
|
||||||
|
raw, cal = 0.5, 50.0
|
||||||
|
models[target] = {
|
||||||
|
"description": model.target_def["description"],
|
||||||
|
"raw": round(raw * 100, 1),
|
||||||
|
"calibrated": round(cal, 1),
|
||||||
|
"method": model.calibrator.method,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Typhoon
|
||||||
|
typhoon = {}
|
||||||
|
if predictor._last_typhoon:
|
||||||
|
for k in ["typhoon_T1", "typhoon_T3", "typhoon_T8",
|
||||||
|
"typhoon_T8_72h", "typhoon_T8_120h"]:
|
||||||
|
if k in predictor._last_typhoon:
|
||||||
|
typhoon[k] = round(predictor._last_typhoon[k], 1)
|
||||||
|
|
||||||
|
# Spatial
|
||||||
|
spatial = predictor._last_spatial or {}
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"fetch_time": datetime.now().strftime("%H:%M:%S"),
|
||||||
|
"models": models,
|
||||||
|
"typhoon": typhoon,
|
||||||
|
"spatial": spatial,
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template_string(HTML_TEMPLATE)
|
return render_template_string(HTML_TEMPLATE)
|
||||||
|
|||||||
Reference in New Issue
Block a user