Badumetsibb commited on
Commit
3a0e72b
·
verified ·
1 Parent(s): fc02711

Upload adaptive_meta_patch_v1.py

Browse files
Files changed (1) hide show
  1. adaptive_meta_patch_v1.py +257 -0
adaptive_meta_patch_v1.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # ===============================================
3
+ # ADAPTIVE META-CONTROLLER MAIN LOOP (V1)
4
+ # Drop-in for app.py — replaces your main_worker.
5
+ # ===============================================
6
+
7
+ import os
8
+ import csv
9
+ import time
10
+ import math
11
+ import random
12
+ from collections import deque, defaultdict
13
+
14
+ import pandas as pd
15
+
16
+ # ------------------ Meta Components ------------------
17
+
18
+ class PerformanceLogger:
19
+ \"\"\"Append signals and outcomes to a CSV for meta-learning and replay.\"\"\"
20
+ def __init__(self, path=\"/mnt/data/agent_signals_log.csv\"):
21
+ self.path = path
22
+ header = [\"timestamp\",\"strategy\",\"action\",\"entry\",\"stop_loss\",\"take_profit\",\"price_at_signal\",\"eval_time\",\"pnl\",\"reward\",\"context_hash\"]
23
+ if not os.path.exists(self.path):
24
+ with open(self.path, \"w\", newline='') as f:
25
+ writer = csv.writer(f)
26
+ writer.writerow(header)
27
+ def log_signal(self, ts, strategy, action, entry, sl, tp, price, eval_time, context_hash):
28
+ with open(self.path, \"a\", newline='') as f:
29
+ writer = csv.writer(f)
30
+ writer.writerow([ts, strategy, action, entry, sl, tp, price, eval_time, \"\", \"\", context_hash])
31
+ def update_outcome(self, ts, pnl, reward):
32
+ rows = []
33
+ filled = False
34
+ with open(self.path, \"r\", newline='') as f:
35
+ rows = list(csv.reader(f))
36
+ for i in range(len(rows)-1, 0, -1):
37
+ if rows[i][0] == ts and rows[i][8] == \"\":
38
+ rows[i][8] = f\"{pnl:.6f}\"
39
+ rows[i][9] = f\"{reward:.6f}\"
40
+ filled = True
41
+ break
42
+ if filled:
43
+ with open(self.path, \"w\", newline='') as f:
44
+ writer = csv.writer(f)
45
+ writer.writerows(rows)
46
+
47
+ class PageHinkley:
48
+ \"\"\"Page-Hinkley change detector for streaming losses/returns.\"\"\"
49
+ def __init__(self, delta=0.0001, lambda_=40, alpha=1-1e-3):
50
+ self.mean = 0.0
51
+ self.delta = delta
52
+ self.lambda_ = lambda_
53
+ self.alpha = alpha
54
+ self.cumulative = 0.0
55
+ def update(self, x):
56
+ # x: score (e.g., negative pnl or error)
57
+ self.mean = self.mean * self.alpha + x * (1 - self.alpha)
58
+ self.cumulative = min(self.cumulative + x - self.mean - self.delta, 0)
59
+ if -self.cumulative > self.lambda_:
60
+ self.cumulative = 0
61
+ return True
62
+ return False
63
+
64
+ class ThompsonBandit:
65
+ \"\"\"Thompson sampling bandit with Bernoulli reward (win/loss).\"\"\"
66
+ def __init__(self, strategies):
67
+ self.strategies = list(strategies)
68
+ self.success = {s: 1 for s in self.strategies} # Beta(1,1) priors
69
+ self.fail = {s: 1 for s in self.strategies}
70
+ def select(self, context=None):
71
+ samples = {s: random.betavariate(self.success[s], self.fail[s]) for s in self.strategies}
72
+ return max(samples, key=samples.get)
73
+ def update(self, strategy, reward_binary):
74
+ if reward_binary >= 1:
75
+ self.success[strategy] += 1
76
+ else:
77
+ self.fail[strategy] += 1
78
+
79
+ class StrategyManager:
80
+ \"\"\"Wrap strategies with a uniform callable interface.\"\"\"
81
+ def __init__(self, situation_room, extra_strategies=None):
82
+ self.situation_room = situation_room
83
+ self.extra = extra_strategies or {}
84
+ def list_strategies(self):
85
+ # Provide your canonical rule-based strategy
86
+ # The exact signature for generate_thesis may differ in your code.
87
+ def rule_based(seq):
88
+ # You may customize how horizons/params are passed from your BEST_PARAMS
89
+ return self.situation_room.generate_thesis({}, seq)
90
+ all_strat = {\"rule_based\": rule_based}
91
+ all_strat.update(self.extra)
92
+ return all_strat
93
+
94
+ # ------------------ Small helpers ------------------
95
+
96
+ def context_hash_from_df(df):
97
+ r = df.iloc[-1]
98
+ keys = [k for k in [\"close\",\"ATR\",\"EMA_20\",\"RSI\",\"session_london\"] if k in r.index]
99
+ vals = [f\"{r[k]:.6f}\" for k in keys]
100
+ return \"_\".join(vals) if vals else f\"{float(r.get('close', 0.0)):.6f}\"
101
+
102
+ def fetch_current_price_or_last(seq):
103
+ try:
104
+ return float(seq.iloc[-1]['close'])
105
+ except Exception:
106
+ return float(seq['close'].iloc[-1])
107
+
108
+ # ------------------ Evaluation pass ------------------
109
+
110
+ def evaluate_pending_signals(perf_logger_path, bandit, change_detector, price_fetch_seq):
111
+ now = pd.Timestamp.now(tz='UTC')
112
+ rows = []
113
+ updated = False
114
+ try:
115
+ with open(perf_logger_path, \"r\", newline='') as f:
116
+ rows = list(csv.reader(f))
117
+ except FileNotFoundError:
118
+ return
119
+ for i in range(1, len(rows)):
120
+ if rows[i][8] != \"\": # already evaluated
121
+ continue
122
+ eval_time_str = rows[i][7]
123
+ try:
124
+ eval_time = pd.to_datetime(eval_time_str)
125
+ except Exception:
126
+ continue
127
+ if eval_time <= now:
128
+ strategy = rows[i][1]; action = rows[i][2]
129
+ try:
130
+ entry = float(rows[i][3])
131
+ except Exception:
132
+ continue
133
+ price_now = fetch_current_price_or_last(price_fetch_seq())
134
+ pnl = (price_now - entry) if action == \"BUY\" else (entry - price_now)
135
+ reward = 1.0 if pnl > 0 else 0.0
136
+ rows[i][8] = f\"{pnl:.6f}\"
137
+ rows[i][9] = f\"{reward:.6f}\"
138
+ bandit.update(strategy, reward)
139
+ _ = change_detector.update(-pnl)
140
+ updated = True
141
+ if updated:
142
+ with open(perf_logger_path, \"w\", newline='') as f:
143
+ writer = csv.writer(f)
144
+ writer.writerows(rows)
145
+
146
+ # ------------------ Bootstrap dependencies ------------------
147
+
148
+ def bootstrap_components(symbol):
149
+ \"\"\"Create or load your core app components.
150
+ If your app constructs these elsewhere, replace this with imports/uses of your instances.
151
+ \"\"\"
152
+ # Prediction engine: assumes a class PredictionEngine() exists in your app
153
+ try:
154
+ pred_engine = PredictionEngine(symbol=symbol)
155
+ except Exception:
156
+ pred_engine = None # If you don't have it or construct elsewhere
157
+ # Situation room & regime filter
158
+ try:
159
+ sr = RuleBasedSituationRoom(BEST_PARAMS)
160
+ except Exception:
161
+ sr = RuleBasedSituationRoom({})
162
+ try:
163
+ rf = MarketRegimeFilter()
164
+ except Exception:
165
+ class _DummyRF:
166
+ def should_trade(self, regime, thesis): return True
167
+ rf = _DummyRF()
168
+ return pred_engine, sr, rf
169
+
170
+ # ------------------ NEW main_worker ------------------
171
+
172
+ def main_worker(symbol: str, ntfy_topic: str, poll_interval_seconds: int = 60, lookback_minutes: int = 240, eval_horizon_minutes: int = 30):
173
+ \"\"\"Adaptive, self-evaluating main loop.
174
+ Replaces your existing main_worker. Safe to run in paper mode.
175
+ \"\"\"
176
+ pred_engine, situation_room, regime_filter = bootstrap_components(symbol)
177
+ strategy_manager = StrategyManager(situation_room, extra_strategies={
178
+ # Example alt strategy: a tiny scalp variant built on top of your situation room.
179
+ \"scalp\": lambda seq: situation_room.generate_thesis({}, seq)
180
+ })
181
+ bandit = ThompsonBandit(strategy_manager.list_strategies().keys())
182
+ perf_logger = PerformanceLogger()
183
+ change_detector = PageHinkley(delta=0.0001, lambda_=40)
184
+
185
+ def _price_seq_provider():
186
+ # Replace with your data fetcher to get the latest window
187
+ return fetch_latest_sequence(symbol, lookback_minutes)
188
+
189
+ print(\"[Adaptive] main_worker started.\")
190
+ while True:
191
+ try:
192
+ # 1) Fetch latest window + build features
193
+ input_sequence = _price_seq_provider()
194
+ if input_sequence is None or len(input_sequence) < 10:
195
+ time.sleep(poll_interval_seconds); continue
196
+
197
+ features = create_feature_set_for_inference(input_sequence)
198
+
199
+ # 2) Predict (optional): if you have a prediction_engine, use it to enrich features
200
+ if pred_engine is not None and hasattr(pred_engine, \"predict\"):
201
+ try:
202
+ _ = pred_engine.predict(features)
203
+ except Exception as _e:
204
+ pass
205
+
206
+ # 3) Regime classification (optional): if you have a function, call it; else set default
207
+ current_regime = \"normal\"
208
+
209
+ # 4) Strategy selection and signal
210
+ available = strategy_manager.list_strategies()
211
+ chosen_name = bandit.select(context=None)
212
+ trade_thesis = available[chosen_name](features)
213
+
214
+ is_tradeable = True
215
+ try:
216
+ is_tradeable = regime_filter.should_trade(current_regime, trade_thesis)
217
+ except Exception:
218
+ pass
219
+
220
+ final_action = trade_thesis.get('action', 'NO ACTION')
221
+ if not is_tradeable:
222
+ final_action = \"NO TRADE (FILTERED)\"
223
+
224
+ # 5) Log signal for later evaluation
225
+ ts = str(pd.Timestamp.now(tz='UTC'))
226
+ context_hash = context_hash_from_df(features)
227
+ if final_action in [\"BUY\", \"SELL\"]:
228
+ perf_logger.log_signal(
229
+ ts, chosen_name, final_action,
230
+ trade_thesis.get('entry', features.iloc[-1]['close']),
231
+ trade_thesis.get('stop_loss', None),
232
+ trade_thesis.get('take_profit', None),
233
+ float(features.iloc[-1]['close']),
234
+ (pd.Timestamp.now(tz='UTC') + pd.Timedelta(minutes=eval_horizon_minutes)).isoformat(),
235
+ context_hash
236
+ )
237
+ # Notify
238
+ try:
239
+ send_ntfy_notification(ntfy_topic, trade_thesis | {\"strategy\": chosen_name})
240
+ except Exception:
241
+ pass
242
+
243
+ # 6) Evaluate pending signals (shadow P&L)
244
+ evaluate_pending_signals(perf_logger.path, bandit, change_detector, _price_seq_provider)
245
+
246
+ # 7) Optional: trigger fine-tune on drift
247
+ # You can check the internal state of change_detector if you adapt the class to expose flags.
248
+
249
+ time.sleep(poll_interval_seconds)
250
+
251
+ except KeyboardInterrupt:
252
+ print(\"[Adaptive] Stopping main_worker.\")
253
+ break
254
+ except Exception as e:
255
+ # Keep the loop resilient
256
+ print(f\"[Adaptive] Loop error: {e}\")
257
+ time.sleep(poll_interval_seconds)