ekjotsingh commited on
Commit
05285a1
·
verified ·
1 Parent(s): c13b815

Update backtest.py

Browse files
Files changed (1) hide show
  1. backtest.py +34 -41
backtest.py CHANGED
@@ -24,18 +24,12 @@ def load_universe():
24
  df = df[df['ListDate'] < pd.to_datetime("2010-01-01")]
25
 
26
  tickers = [f"{x}.NS" for x in df['SYMBOL'].tolist()]
27
- print(f"✅ Loaded {len(tickers)} historical tickers for Market-Wide AI Evaluation.")
28
  random.shuffle(tickers)
29
  return tickers[:250]
30
  except:
31
  return ["RELIANCE.NS", "TCS.NS", "INFY.NS", "SBIN.NS", "HDFCBANK.NS", "ITC.NS"]
32
 
33
- def simulate_options(nifty_ret, trend_strength):
34
- leverage = 1.0
35
- if trend_strength > 0.05: leverage = 2.0
36
- elif trend_strength < -0.05: leverage = -2.0
37
- return (nifty_ret * leverage) - 0.0005
38
-
39
  def run_strategy_genome(data, genome):
40
  if data.empty: return -1.0, []
41
 
@@ -50,9 +44,10 @@ def run_strategy_genome(data, genome):
50
  top_n = int(genome['top_n'])
51
  rebalance_days = int(genome['rebalance'])
52
  stop_loss = float(genome['stop_loss'])
 
53
 
54
  momentum = stocks.pct_change(lookback)
55
- nifty_ma = nifty.rolling(200).mean()
56
 
57
  curve = [INITIAL_CAPITAL]
58
  curr_val = INITIAL_CAPITAL
@@ -66,33 +61,33 @@ def run_strategy_genome(data, genome):
66
  nxt = dates[min(i+rebalance_days, len(dates)-1)]
67
 
68
  try:
69
- trend_strength = (nifty.loc[curr] / nifty_ma.loc[curr]) - 1
70
- is_bull = trend_strength > 0
71
  period_ret = 0.0
72
 
73
  if is_bull:
 
74
  if curr in momentum.index:
75
  scores = momentum.loc[curr]
76
- scores = scores[scores > 0]
77
  picks = scores.sort_values(ascending=False).head(top_n).index.tolist()
78
 
79
  if len(picks) > 0:
80
  p1 = stocks.loc[curr, picks]
81
  p2 = stocks.loc[nxt, picks]
 
 
82
  stock_ret = ((p2 - p1) / p1).mean()
83
  if pd.isna(stock_ret): stock_ret = 0.0
84
 
85
- n_ret = (nifty.loc[nxt] - nifty.loc[curr]) / nifty.loc[curr]
86
- opt_ret = simulate_options(n_ret, trend_strength)
87
-
88
- period_ret = (0.8 * stock_ret) + (0.2 * opt_ret)
89
  else:
 
90
  g_ret = (gold.loc[nxt] - gold.loc[curr]) / gold.loc[curr]
91
- n_ret = (nifty.loc[nxt] - nifty.loc[curr]) / nifty.loc[curr]
92
- put_ret = simulate_options(n_ret, trend_strength)
93
- period_ret = (0.5 * g_ret) + (0.5 * put_ret)
94
 
95
- if pd.isna(period_ret): period_ret = 0.0
96
  if period_ret < -stop_loss: period_ret = -stop_loss
97
 
98
  curr_val = curr_val * (1 + period_ret)
@@ -112,7 +107,7 @@ def run_strategy_genome(data, genome):
112
  return cagr, pd.Series(curve, index=sim_dates)
113
 
114
  def backtest_engine():
115
- print(f"⚙️ Initializing Hyper-Evolution Market Simulator...")
116
  start_time = time.time()
117
 
118
  tickers = load_universe()
@@ -126,23 +121,24 @@ def backtest_engine():
126
  data = data.ffill().bfill()
127
  if data.empty: return None
128
 
 
129
  population = []
130
  for _ in range(30):
131
  population.append({
132
- 'lookback': random.choice([10, 20, 30, 45, 60, 90, 120]),
133
- 'top_n': random.choice([5, 8, 10, 15, 20]),
134
- 'rebalance': random.choice([3, 5, 7, 10, 14, 21]),
135
- 'stop_loss': random.choice([0.02, 0.04, 0.06, 0.08, 0.10])
 
136
  })
137
 
138
  best_cagr = -1.0
139
- prev_cagr = -1.0
140
  best_curve = None
141
  stall_count = 0
142
  generation = 1
143
 
144
  while (time.time() - start_time) < (SIMULATION_TIME_MIN * 60):
145
- print(f"\n🧬 Generation {generation}: Evaluating Portfolios...")
146
  results = []
147
 
148
  for genome in population:
@@ -154,7 +150,6 @@ def backtest_engine():
154
  if results:
155
  current_top_cagr = results[0][0]
156
 
157
- # Stall Detection Logic
158
  if current_top_cagr > best_cagr + 0.001:
159
  best_cagr = current_top_cagr
160
  best_curve = results[0][1]
@@ -164,17 +159,15 @@ def backtest_engine():
164
  stall_count += 1
165
 
166
  print(f" 🏆 Best: {best_cagr*100:.1f}% CAGR")
167
- print(f" 🧬 Structure: Lookback {best_dna['lookback']}d | Basket of {best_dna['top_n']} Stocks | Rebalances Every {best_dna['rebalance']} Days | Stop Loss: {best_dna['stop_loss']*100}%")
168
 
169
  if stall_count >= 3:
170
- print(" ⚠️ Genetic Bottleneck Detected. Triggering Hyper-Mutation Event!")
171
 
172
- survivors = [x[2] for x in results[:6]] # Keep absolute best 6
173
  new_pop = list(survivors)
174
 
175
- # --- CROSSOVER & MUTATION ENGINE ---
176
  while len(new_pop) < 30:
177
- # Mating: Combine traits from two top strategies
178
  p1 = random.choice(survivors)
179
  p2 = random.choice(survivors)
180
 
@@ -182,20 +175,20 @@ def backtest_engine():
182
  'lookback': p1['lookback'] if random.random() > 0.5 else p2['lookback'],
183
  'top_n': p1['top_n'] if random.random() > 0.5 else p2['top_n'],
184
  'rebalance': p1['rebalance'] if random.random() > 0.5 else p2['rebalance'],
185
- 'stop_loss': p1['stop_loss'] if random.random() > 0.5 else p2['stop_loss']
 
186
  }
187
 
188
- # Hyper-Mutation triggers if stuck
189
  mutation_rate = 0.8 if stall_count >= 3 else 0.3
190
 
191
- if random.random() < mutation_rate: child['lookback'] = random.choice([10, 20, 30, 45, 60, 90, 120])
192
- if random.random() < mutation_rate: child['top_n'] = random.choice([5, 8, 10, 15, 20])
193
- if random.random() < mutation_rate: child['rebalance'] = random.choice([3, 5, 7, 10, 14, 21])
194
- if random.random() < mutation_rate: child['stop_loss'] = random.choice([0.02, 0.04, 0.06, 0.08, 0.10])
 
195
 
196
  new_pop.append(child)
197
 
198
- # If stuck for 4 rounds, wipe the board except for the #1 strategy
199
  if stall_count >= 4:
200
  stall_count = 0
201
 
@@ -209,14 +202,14 @@ def backtest_engine():
209
 
210
  if best_curve is not None:
211
  plt.figure(figsize=(12, 7))
212
- plt.plot(best_curve, label=f"Evolved Diversified Strategy ({best_cagr*100:.1f}%)", color='blue', linewidth=2)
213
 
214
  nifty = data["^NSEI"]
215
  bench = (nifty.loc[best_curve.index] / nifty.loc[best_curve.index[0]]) * INITIAL_CAPITAL
216
  plt.plot(bench, label="Nifty 50 Index", color='gray', linestyle='--')
217
 
218
  plt.yscale('log')
219
- plt.title("Renaissance Diversified Engine (Basket Trading)")
220
  plt.ylabel("Portfolio Value (Log Scale)")
221
  plt.legend()
222
  plt.grid(True, alpha=0.3)
 
24
  df = df[df['ListDate'] < pd.to_datetime("2010-01-01")]
25
 
26
  tickers = [f"{x}.NS" for x in df['SYMBOL'].tolist()]
27
+ print(f"✅ Loaded {len(tickers)} historical tickers for Pure Alpha Engine.")
28
  random.shuffle(tickers)
29
  return tickers[:250]
30
  except:
31
  return ["RELIANCE.NS", "TCS.NS", "INFY.NS", "SBIN.NS", "HDFCBANK.NS", "ITC.NS"]
32
 
 
 
 
 
 
 
33
  def run_strategy_genome(data, genome):
34
  if data.empty: return -1.0, []
35
 
 
44
  top_n = int(genome['top_n'])
45
  rebalance_days = int(genome['rebalance'])
46
  stop_loss = float(genome['stop_loss'])
47
+ trend_filter_days = int(genome['trend_filter']) # AI chosen crash detector
48
 
49
  momentum = stocks.pct_change(lookback)
50
+ nifty_ma = nifty.rolling(trend_filter_days).mean()
51
 
52
  curve = [INITIAL_CAPITAL]
53
  curr_val = INITIAL_CAPITAL
 
61
  nxt = dates[min(i+rebalance_days, len(dates)-1)]
62
 
63
  try:
64
+ # 1.0x Maximum Capital Exposure Rule Enforced Here
65
+ is_bull = nifty.loc[curr] > nifty_ma.loc[curr]
66
  period_ret = 0.0
67
 
68
  if is_bull:
69
+ # ZERO LEVERAGE: 100% Cash Equities
70
  if curr in momentum.index:
71
  scores = momentum.loc[curr]
72
+ scores = scores[scores > 0] # Must have positive absolute momentum
73
  picks = scores.sort_values(ascending=False).head(top_n).index.tolist()
74
 
75
  if len(picks) > 0:
76
  p1 = stocks.loc[curr, picks]
77
  p2 = stocks.loc[nxt, picks]
78
+
79
+ # 100% of money split evenly among the tight basket
80
  stock_ret = ((p2 - p1) / p1).mean()
81
  if pd.isna(stock_ret): stock_ret = 0.0
82
 
83
+ period_ret = stock_ret
 
 
 
84
  else:
85
+ # ZERO LEVERAGE BEAR HEDGE: 100% Gold (Safe Haven Investing)
86
  g_ret = (gold.loc[nxt] - gold.loc[curr]) / gold.loc[curr]
87
+ if pd.isna(g_ret): g_ret = 0.0
88
+ period_ret = g_ret
 
89
 
90
+ # Stop Loss Execution
91
  if period_ret < -stop_loss: period_ret = -stop_loss
92
 
93
  curr_val = curr_val * (1 + period_ret)
 
107
  return cagr, pd.Series(curve, index=sim_dates)
108
 
109
  def backtest_engine():
110
+ print(f"⚙️ Initializing Zero-Leverage 'Pure Alpha' Simulator...")
111
  start_time = time.time()
112
 
113
  tickers = load_universe()
 
121
  data = data.ffill().bfill()
122
  if data.empty: return None
123
 
124
+ # --- AI GENOME EXPANSION ---
125
  population = []
126
  for _ in range(30):
127
  population.append({
128
+ 'lookback': random.choice([5, 10, 15, 20, 30, 45, 60]), # Faster signal
129
+ 'top_n': random.choice([2, 3, 4, 5]), # Tight Sector Basket
130
+ 'rebalance': random.choice([2, 3, 5, 7, 10]), # High Velocity
131
+ 'stop_loss': random.choice([0.02, 0.04, 0.06]), # Tight Risk
132
+ 'trend_filter': random.choice([30, 50, 100, 200]) # AI decides crash sensitivity
133
  })
134
 
135
  best_cagr = -1.0
 
136
  best_curve = None
137
  stall_count = 0
138
  generation = 1
139
 
140
  while (time.time() - start_time) < (SIMULATION_TIME_MIN * 60):
141
+ print(f"\n🧬 Generation {generation}: Evaluating Portfolios (Max 1.0x Exposure)...")
142
  results = []
143
 
144
  for genome in population:
 
150
  if results:
151
  current_top_cagr = results[0][0]
152
 
 
153
  if current_top_cagr > best_cagr + 0.001:
154
  best_cagr = current_top_cagr
155
  best_curve = results[0][1]
 
159
  stall_count += 1
160
 
161
  print(f" 🏆 Best: {best_cagr*100:.1f}% CAGR")
162
+ print(f" 🧬 DNA: Lookback {best_dna['lookback']}d | Holds Top {best_dna['top_n']} Stocks | Rebalance: {best_dna['rebalance']}d | Regime Filter: {best_dna['trend_filter']}d SMA")
163
 
164
  if stall_count >= 3:
165
+ print(" ⚠️ Evolution Stalled. Triggering Mass Mutation Event!")
166
 
167
+ survivors = [x[2] for x in results[:6]]
168
  new_pop = list(survivors)
169
 
 
170
  while len(new_pop) < 30:
 
171
  p1 = random.choice(survivors)
172
  p2 = random.choice(survivors)
173
 
 
175
  'lookback': p1['lookback'] if random.random() > 0.5 else p2['lookback'],
176
  'top_n': p1['top_n'] if random.random() > 0.5 else p2['top_n'],
177
  'rebalance': p1['rebalance'] if random.random() > 0.5 else p2['rebalance'],
178
+ 'stop_loss': p1['stop_loss'] if random.random() > 0.5 else p2['stop_loss'],
179
+ 'trend_filter': p1['trend_filter'] if random.random() > 0.5 else p2['trend_filter']
180
  }
181
 
 
182
  mutation_rate = 0.8 if stall_count >= 3 else 0.3
183
 
184
+ if random.random() < mutation_rate: child['lookback'] = random.choice([5, 10, 15, 20, 30, 45, 60])
185
+ if random.random() < mutation_rate: child['top_n'] = random.choice([2, 3, 4, 5])
186
+ if random.random() < mutation_rate: child['rebalance'] = random.choice([2, 3, 5, 7, 10])
187
+ if random.random() < mutation_rate: child['stop_loss'] = random.choice([0.02, 0.04, 0.06])
188
+ if random.random() < mutation_rate: child['trend_filter'] = random.choice([30, 50, 100, 200])
189
 
190
  new_pop.append(child)
191
 
 
192
  if stall_count >= 4:
193
  stall_count = 0
194
 
 
202
 
203
  if best_curve is not None:
204
  plt.figure(figsize=(12, 7))
205
+ plt.plot(best_curve, label=f"Zero-Leverage AI Strategy ({best_cagr*100:.1f}%)", color='blue', linewidth=2)
206
 
207
  nifty = data["^NSEI"]
208
  bench = (nifty.loc[best_curve.index] / nifty.loc[best_curve.index[0]]) * INITIAL_CAPITAL
209
  plt.plot(bench, label="Nifty 50 Index", color='gray', linestyle='--')
210
 
211
  plt.yscale('log')
212
+ plt.title("Renaissance Pure Alpha Engine (0x Leverage / 100% Cash)")
213
  plt.ylabel("Portfolio Value (Log Scale)")
214
  plt.legend()
215
  plt.grid(True, alpha=0.3)