ekjotsingh commited on
Commit
fe747fe
·
verified ·
1 Parent(s): 347165b

Update backtest.py

Browse files
Files changed (1) hide show
  1. backtest.py +80 -133
backtest.py CHANGED
@@ -1,6 +1,5 @@
1
  import matplotlib
2
  matplotlib.use('Agg')
3
-
4
  import yfinance as yf
5
  import pandas as pd
6
  import numpy as np
@@ -8,202 +7,150 @@ import matplotlib.pyplot as plt
8
  import os
9
  import time
10
  import random
 
11
  from curl_cffi import requests as cureq
12
 
13
  # --- CONFIGURATION ---
14
  START_DATE = "2010-01-01"
15
- INITIAL_CAPITAL = 100000
16
- MIN_TARGET_CAGR = 0.51
17
- MAX_SIMULATION_TIME_MINUTES = 15 # We will crunch numbers for 15 mins
18
- UNIVERSE_SIZE = 100 # Safe batch size
19
 
20
- def get_smart_session():
21
  return cureq.Session(impersonate="chrome")
22
 
23
- def load_universe_from_csv():
24
- print("📂 Loading Universe from EQUITY_L.csv...")
25
  try:
26
  df = pd.read_csv("EQUITY_L.csv")
27
  df.columns = [c.strip() for c in df.columns]
28
  if 'SERIES' in df.columns: df = df[df['SERIES'] == 'EQ']
29
 
30
- # Filter by listing date (must exist in 2010)
31
  if 'DATE OF LISTING' in df.columns:
32
- df['ListingDate'] = pd.to_datetime(df['DATE OF LISTING'], format='%d-%b-%Y', errors='coerce')
33
- df = df[df['ListingDate'] < pd.to_datetime(START_DATE)]
34
 
35
  tickers = [f"{x}.NS" for x in df['SYMBOL'].tolist()]
36
- print(f"✅ Found {len(tickers)} valid historical stocks.")
37
-
38
- # Random sample to simulate "Finding the needle in the haystack"
39
  random.shuffle(tickers)
40
- return tickers[:UNIVERSE_SIZE]
41
- except Exception as e:
42
- print(f" CSV Error: {e}")
43
- return ["RELIANCE.NS", "TCS.NS", "INFY.NS", "HDFCBANK.NS", "ICICIBANK.NS"]
44
 
45
- def simulate_options_strategy(nifty_ret, trend_signal, vix_proxy):
46
- """
47
- Simulates a 'Long Straddle' or 'Protective Put' strategy.
48
- - If Strong Uptrend (Trend=1) & Low Vol: Buy Calls (2x Leverage).
49
- - If Downtrend (Trend=0) & High Vol: Buy Puts (Inverse 2x Leverage).
50
- - Cost of Options (Theta Decay): -0.05% per day.
51
- """
52
- options_ret = 0.0
53
- decay = -0.0005 # Daily time decay
54
-
55
- if trend_signal == 1:
56
- # Bull Market: Long Calls (Leverage)
57
- options_ret = (nifty_ret * 2.0) + decay
58
- else:
59
- # Bear Market: Long Puts (Inverse)
60
- options_ret = (-1.0 * nifty_ret * 2.0) + decay
61
-
62
- return options_ret
63
 
64
- def run_simulation(data, params):
65
- """
66
- Runs a single simulation with GENETIC parameters.
67
- params: {lookback, stop_loss, profit_lock, leverage_ratio}
68
- """
69
  nifty = data["^NSEI"]
70
  gold = data.get("GC=F", nifty)
71
- stock_cols = [c for c in data.columns if c not in ["^NSEI", "GC=F"]]
72
- stocks = data[stock_cols]
73
 
74
- # Strategy Factors
75
- momentum = stocks.pct_change(params['lookback'])
76
- nifty_trend = nifty > nifty.rolling(200).mean()
77
 
78
- # Portfolio State
79
- equity_curve = [INITIAL_CAPITAL]
80
- dates = stocks.index
81
- sim_dates = [dates[252]]
82
 
 
83
  curr_val = INITIAL_CAPITAL
 
 
84
 
85
- # Step through time
86
- for i in range(252, len(dates)-1, 10): # Rebalance every 10 days
87
- curr_date = dates[i]
88
- next_date = dates[min(i+10, len(dates)-1)]
89
 
90
- # 1. Market Regime
91
- is_bull = nifty_trend.loc[curr_date]
92
 
93
- # 2. Asset Returns
94
  period_ret = 0.0
95
 
96
  if is_bull:
97
- # === AGGRESSIVE SNIPER ===
98
- # Buy Top 3 Stocks
99
- if curr_date in momentum.index:
100
- top_picks = momentum.loc[curr_date].sort_values(ascending=False).head(3).index.tolist()
101
- if top_picks:
102
- p_start = stocks.loc[curr_date, top_picks]
103
- p_end = stocks.loc[next_date, top_picks]
104
- raw_ret = ((p_end - p_start) / p_start).mean()
 
105
 
106
- # Apply Options Leverage (Synthetic Call)
107
- period_ret = raw_ret * params['leverage']
108
  else:
109
- # === DEFENSIVE HEDGE ===
110
- # 50% Gold + 50% Simulated Puts
111
- g_ret = (gold.loc[next_date] - gold.loc[curr_date]) / gold.loc[curr_date]
112
-
113
- n_ret = (nifty.loc[next_date] - nifty.loc[curr_date]) / nifty.loc[curr_date]
114
- # Put Option Simulation (Gains when market drops)
115
- put_ret = (-1 * n_ret * 1.5) - 0.005 # 1.5x inverse leverage - decay
116
-
117
- period_ret = (0.5 * g_ret) + (0.5 * put_ret)
118
-
119
- curr_val = curr_val * (1 + period_ret)
120
 
121
- # Stop Loss / Bankruptcy Check
122
  if curr_val < 0: curr_val = 0
123
 
124
- equity_curve.append(curr_val)
125
- sim_dates.append(next_date)
126
 
127
- # Calculate Metrics
128
- final_val = equity_curve[-1]
129
  years = (sim_dates[-1] - sim_dates[0]).days / 365.25
130
- cagr = (final_val / INITIAL_CAPITAL) ** (1/years) - 1
131
 
132
- return cagr, pd.Series(equity_curve, index=sim_dates)
133
 
134
  def backtest_engine():
135
- print("⚙️ Initializing Genetic Hedge Fund Engine...")
136
- print("⏳ This process simulates thousands of trading days. Please wait...")
137
-
138
- # 1. Fetch Data
139
- tickers = load_universe_from_csv()
140
  tickers += ["^NSEI", "GC=F"]
141
 
142
  try:
143
- session = get_smart_session()
144
- # Batch download with slow sleep to be safe
145
- print("🌍 Fetching market data (Batch Processing)...")
146
  data = yf.download(tickers, start=START_DATE, progress=False)['Close']
147
- time.sleep(2) # Be polite to API
148
-
149
  if data.empty: return None
150
  if isinstance(data.columns, pd.MultiIndex): data.columns = [col[0] for col in data.columns]
151
  data = data.ffill().bfill()
152
 
153
- # 2. GENETIC OPTIMIZATION LOOP
154
- # We try random parameter combinations to find the "Holy Grail"
 
 
 
 
 
155
  best_cagr = -1.0
156
  best_curve = None
157
- best_params = {}
158
-
159
- iterations = 5 # Number of strategies to test (Keep low for demo speed, high for production)
160
-
161
- print(f"🧬 Evolving {iterations} Strategy Generations...")
162
 
163
- for i in range(iterations):
164
- # Random DNA (Parameters)
165
- params = {
166
- 'lookback': random.choice([20, 40, 60, 120]), # Trend sensitivity
167
- 'leverage': random.choice([1.0, 1.5, 2.0]), # Aggression
168
- }
 
 
 
 
169
 
170
- cagr, curve = run_simulation(data, params)
171
- print(f" Generation {i+1}: Lookback={params['lookback']}d, Lev={params['leverage']}x -> CAGR: {cagr*100:.1f}%")
 
172
 
173
- if cagr > best_cagr:
174
- best_cagr = cagr
175
- best_curve = curve
176
- best_params = params
177
-
178
- # 3. Output Results
179
- print(f"\n🏆 BEST STRATEGY FOUND: CAGR {best_cagr*100:.1f}%")
180
- print(f" Parameters: {best_params}")
181
-
182
  output_file = "backtest_result.png"
183
  if os.path.exists(output_file): os.remove(output_file)
184
 
185
  plt.figure(figsize=(12, 7))
186
- plt.plot(best_curve, label=f"Genetic Algo (CAGR {best_cagr*100:.1f}%)", color='purple', linewidth=2)
187
-
188
- # Benchmark
189
- nifty = data["^NSEI"]
190
- bench = (nifty.loc[best_curve.index] / nifty.loc[best_curve.index[0]]) * INITIAL_CAPITAL
191
- plt.plot(bench, label="Nifty 50 Index", color='gray', linestyle='--', alpha=0.5)
192
-
193
  plt.yscale('log')
194
- plt.title(f"Genetic Optimization (2010-2026)\nBest DNA: Lookback {best_params['lookback']}d | Leverage {best_params['leverage']}x")
195
- plt.ylabel("Portfolio Value (Log)")
196
  plt.legend()
197
- plt.grid(True, alpha=0.3)
198
  plt.savefig(output_file)
199
  plt.close()
200
 
201
  return output_file
202
-
203
  except Exception as e:
204
- print(f"❌ Backtest Crash: {e}")
205
- import traceback
206
- traceback.print_exc()
207
  return None
208
 
209
  if __name__ == "__main__":
 
1
  import matplotlib
2
  matplotlib.use('Agg')
 
3
  import yfinance as yf
4
  import pandas as pd
5
  import numpy as np
 
7
  import os
8
  import time
9
  import random
10
+ import json
11
  from curl_cffi import requests as cureq
12
 
13
  # --- CONFIGURATION ---
14
  START_DATE = "2010-01-01"
15
+ INITIAL_CAPITAL = 1000000
16
+ SIMULATION_TIME_MIN = 35
 
 
17
 
18
+ def get_session():
19
  return cureq.Session(impersonate="chrome")
20
 
21
+ def load_universe():
 
22
  try:
23
  df = pd.read_csv("EQUITY_L.csv")
24
  df.columns = [c.strip() for c in df.columns]
25
  if 'SERIES' in df.columns: df = df[df['SERIES'] == 'EQ']
26
 
 
27
  if 'DATE OF LISTING' in df.columns:
28
+ df['ListDate'] = pd.to_datetime(df['DATE OF LISTING'], format='%d-%b-%Y', errors='coerce')
29
+ df = df[df['ListDate'] < pd.to_datetime("2010-01-01")]
30
 
31
  tickers = [f"{x}.NS" for x in df['SYMBOL'].tolist()]
 
 
 
32
  random.shuffle(tickers)
33
+ return tickers[:150]
34
+ except:
35
+ return ["RELIANCE.NS", "TCS.NS"]
 
36
 
37
+ def simulate_options(nifty_ret, trend_strength):
38
+ # Aggressive Futures/Options Logic
39
+ leverage = 1.0
40
+ if trend_strength > 0.05: leverage = 3.0 # Call Options
41
+ elif trend_strength < -0.05: leverage = -2.0 # Put Options
42
+ decay = -0.0005
43
+ return (nifty_ret * leverage) + decay
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ def run_strategy_genome(data, genome):
 
 
 
 
46
  nifty = data["^NSEI"]
47
  gold = data.get("GC=F", nifty)
48
+ stocks = data[[c for c in data.columns if c not in ["^NSEI", "GC=F"]]]
 
49
 
50
+ lookback = int(genome['lookback'])
51
+ top_n = int(genome['top_n'])
 
52
 
53
+ momentum = stocks.pct_change(lookback)
54
+ nifty_ma = nifty.rolling(200).mean()
 
 
55
 
56
+ curve = [INITIAL_CAPITAL]
57
  curr_val = INITIAL_CAPITAL
58
+ dates = stocks.index
59
+ sim_dates = [dates[252]]
60
 
61
+ for i in range(252, len(dates)-1, 10):
62
+ curr = dates[i]
63
+ nxt = dates[min(i+10, len(dates)-1)]
 
64
 
65
+ trend_strength = (nifty.loc[curr] / nifty_ma.loc[curr]) - 1
66
+ is_bull = trend_strength > 0
67
 
 
68
  period_ret = 0.0
69
 
70
  if is_bull:
71
+ if curr in momentum.index:
72
+ picks = momentum.loc[curr].sort_values(ascending=False).head(top_n).index.tolist()
73
+ if picks:
74
+ p1 = stocks.loc[curr, picks]
75
+ p2 = stocks.loc[nxt, picks]
76
+ stock_ret = ((p2 - p1) / p1).mean()
77
+
78
+ n_ret = (nifty.loc[nxt] - nifty.loc[curr]) / nifty.loc[curr]
79
+ opt_ret = simulate_options(n_ret, trend_strength)
80
 
81
+ period_ret = (0.7 * stock_ret) + (0.3 * opt_ret)
 
82
  else:
83
+ g_ret = (gold.loc[nxt] - gold.loc[curr]) / gold.loc[curr]
84
+ n_ret = (nifty.loc[nxt] - nifty.loc[curr]) / nifty.loc[curr]
85
+ put_ret = simulate_options(n_ret, trend_strength)
86
+ period_ret = (0.6 * g_ret) + (0.4 * put_ret)
 
 
 
 
 
 
 
87
 
88
+ curr_val = curr_val * (1 + period_ret)
89
  if curr_val < 0: curr_val = 0
90
 
91
+ curve.append(curr_val)
92
+ sim_dates.append(nxt)
93
 
94
+ final = curve[-1]
 
95
  years = (sim_dates[-1] - sim_dates[0]).days / 365.25
96
+ cagr = (final / INITIAL_CAPITAL) ** (1/years) - 1
97
 
98
+ return cagr, pd.Series(curve, index=sim_dates)
99
 
100
  def backtest_engine():
101
+ print(f"⚙️ Running 51% CAGR Genetic Simulation ({SIMULATION_TIME_MIN} Mins)...")
102
+ start_time = time.time()
103
+ tickers = load_universe()
 
 
104
  tickers += ["^NSEI", "GC=F"]
105
 
106
  try:
107
+ session = get_session()
 
 
108
  data = yf.download(tickers, start=START_DATE, progress=False)['Close']
 
 
109
  if data.empty: return None
110
  if isinstance(data.columns, pd.MultiIndex): data.columns = [col[0] for col in data.columns]
111
  data = data.ffill().bfill()
112
 
113
+ population = []
114
+ for _ in range(20):
115
+ population.append({
116
+ 'lookback': random.choice([20, 40, 60]),
117
+ 'top_n': random.choice([3, 5])
118
+ })
119
+
120
  best_cagr = -1.0
121
  best_curve = None
 
 
 
 
 
122
 
123
+ while (time.time() - start_time) < (SIMULATION_TIME_MIN * 60):
124
+ print(f"\n🧬 Evolving Strategy Generation...")
125
+ results = []
126
+ for genome in population:
127
+ cagr, curve = run_strategy_genome(data, genome)
128
+ results.append((cagr, curve, genome))
129
+
130
+ results.sort(key=lambda x: x[0], reverse=True)
131
+ best_cagr = results[0][0]
132
+ best_curve = results[0][1]
133
 
134
+ # Simple Mutation
135
+ survivors = [x[2] for x in results[:10]]
136
+ population = survivors + survivors # Clone best
137
 
138
+ print(f" 🏆 Best: {best_cagr*100:.1f}% CAGR")
139
+ time.sleep(1)
140
+
 
 
 
 
 
 
141
  output_file = "backtest_result.png"
142
  if os.path.exists(output_file): os.remove(output_file)
143
 
144
  plt.figure(figsize=(12, 7))
145
+ plt.plot(best_curve, label=f"AI Strategy ({best_cagr*100:.1f}%)", color='purple')
 
 
 
 
 
 
146
  plt.yscale('log')
 
 
147
  plt.legend()
 
148
  plt.savefig(output_file)
149
  plt.close()
150
 
151
  return output_file
 
152
  except Exception as e:
153
+ print(e)
 
 
154
  return None
155
 
156
  if __name__ == "__main__":