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:
ramseshk
2026-08-11 11:15:43 +08:00
parent f8dde42007
commit aad4ee8cf6
4 changed files with 732 additions and 4 deletions
+78
View File
@@ -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
1 date bankroll
2 2026-08-11 11:14 1000.0
3 2026-08-11 11:15 1430.571129983699
4 2026-08-11 11:15 1869.9228851208563
5 2026-08-11 11:15 2309.2746402580137
6 2026-08-11 11:15 2593.77145925936
7 2026-08-11 11:15 3033.8015537575993
8 2026-08-11 11:15 3457.8650454555836
9 2026-08-11 11:15 3885.3876428140006
10 2026-08-11 11:15 4385.387642814001
11 2026-08-11 11:15 4885.387642814001
12 2026-08-11 11:15 5385.387642814001
13 2026-08-11 11:15 5885.387642814001
14 2026-08-11 11:15 6057.0007397876425
15 2026-08-11 11:15 6160.7826990978765
16 2026-08-11 11:15 6347.768603660328
17 2026-08-11 11:15 5847.768603660328
18 2026-08-11 11:15 6347.768603660328
19 2026-08-11 11:15 6847.768603660328
20 2026-08-11 11:15 6347.768603660328
21 2026-08-11 11:15 6847.768603660328
22 2026-08-11 11:15 7347.768603660328
23 2026-08-11 11:15 6847.768603660328
24 2026-08-11 11:15 7215.736592572463
25 2026-08-11 11:15 7715.736592572463
26 2026-08-11 11:15 8215.736592572463
27 2026-08-11 11:15 8715.736592572463
28 2026-08-11 11:15 9215.736592572463
29 2026-08-11 11:15 9715.736592572463
30 2026-08-11 11:15 10215.736592572463
31 2026-08-11 11:15 10715.736592572463
32 2026-08-11 11:15 10832.307490599274
33 2026-08-11 11:15 11332.307490599274
34 2026-08-11 11:15 11832.307490599274
35 2026-08-11 11:15 12332.307490599274
36 2026-08-11 11:15 12832.307490599274
37 2026-08-11 11:15 13332.307490599274
38 2026-08-11 11:15 13832.307490599274
39 2026-08-11 11:15 14332.307490599274
40 2026-08-11 11:15 14441.807689556865
41 2026-08-11 11:15 13941.807689556865
42 2026-08-11 11:15 14441.807689556865
43 2026-08-11 11:15 14941.807689556865
44 2026-08-11 11:15 15441.807689556865
45 2026-08-11 11:15 15941.807689556865
46 2026-08-11 11:15 16441.807689556867
47 2026-08-11 11:15 16941.807689556867
48 2026-08-11 11:15 16441.807689556867
49 2026-08-11 11:15 16729.018474036096
50 2026-08-11 11:15 17229.018474036096
51 2026-08-11 11:15 17729.018474036096
52 2026-08-11 11:15 18229.018474036096
53 2026-08-11 11:15 18729.018474036096
54 2026-08-11 11:15 19229.018474036096
55 2026-08-11 11:15 19729.018474036096
56 2026-08-11 11:15 20229.018474036096
57 2026-08-11 11:15 20356.41021319305
58 2026-08-11 11:15 20734.229569661333
59 2026-08-11 11:15 21234.229569661333
60 2026-08-11 11:15 21734.229569661333
61 2026-08-11 11:15 22234.229569661333
62 2026-08-11 11:15 22734.229569661333
63 2026-08-11 11:15 23234.229569661333
64 2026-08-11 11:15 23734.229569661333
65 2026-08-11 11:15 24234.229569661333
66 2026-08-11 11:15 24371.707348301075
67 2026-08-11 11:15 24871.707348301075
68 2026-08-11 11:15 25371.707348301075
69 2026-08-11 11:15 25871.707348301075
70 2026-08-11 11:15 26371.707348301075
71 2026-08-11 11:15 26871.707348301075
72 2026-08-11 11:15 27371.707348301075
73 2026-08-11 11:15 27871.707348301075
74 2026-08-11 11:15 27971.49601527572
75 2026-08-11 11:15 28345.77538017773
76 2026-08-11 11:15 28845.77538017773
77 2026-08-11 11:15 29345.77538017773
78 2026-08-11 11:15 29845.77538017773
+160
View File
@@ -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}
+353
View File
@@ -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
View File
@@ -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)