ekjotsingh commited on
Commit
51a991c
·
verified ·
1 Parent(s): cf599d1

Update backtest.py

Browse files
Files changed (1) hide show
  1. backtest.py +70 -77
backtest.py CHANGED
@@ -6,90 +6,83 @@ import numpy as np
6
  import matplotlib.pyplot as plt
7
  import os
8
 
9
- # --- PARAMETERS (Tweak these to find the best fit) ---
10
  START_DATE = "2004-01-01"
11
- END_DATE = "2026-02-26"
12
- CONFIG = {
13
- "TECH_WEIGHT": 0.4, # Weight of Price/Stochastics
14
- "FUND_WEIGHT": 0.4, # Weight of Quarterly/Financial Health
15
- "SENTIMENT_WEIGHT": 0.2, # Weight of News/Headlines
16
- "VOL_TARGET": 0.18 # Risk ceiling (18% volatility)
17
- }
18
 
19
- def backtest_engine():
20
- # 1. Load Universe (EQUITY_L.csv)
21
- try:
22
- uni_df = pd.read_csv("EQUITY_L.csv")
23
- uni_df.columns = [c.strip() for c in uni_df.columns]
24
- tickers = [f"{s}.NS" for s in uni_df['SYMBOL'].head(50).values] # Top 50 for speed
25
- tickers.append("^NSEI")
26
- except:
27
- tickers = ["RELIANCE.NS", "TCS.NS", "BAJFINANCE.NS", "TITAN.NS", "^NSEI"]
28
-
29
- # 2. Fetch Multi-Source Data
30
- data = yf.download(tickers, start=START_DATE, end=END_DATE, progress=False)['Close']
31
- data = data.ffill().bfill()
32
- benchmark = data['^NSEI']
33
- stocks = data.drop(columns=['^NSEI'])
34
 
35
- # 3. Factor Engines
36
- # Technical: Z-Score (Mean Reversion)
37
- z_score = (stocks - stocks.rolling(20).mean()) / stocks.rolling(20).std()
 
 
 
 
 
38
 
39
- # Fundamental Proxy: (Using P/E & ROE data would be ideal,
40
- # but here we simulate 'Sentiment' as a 60-day acceleration)
41
- sentiment = stocks.pct_change(60)
42
-
43
- # 4. Simulation Loop (Handling 2008 & 2020)
44
- portfolio_val = [100000]
45
- cash = 100000
46
- holdings = {}
47
 
48
- dates = data.index
49
- for i in range(200, len(dates), 5):
50
- date = dates[i]
51
- curr_val = cash + sum(holdings[t] * stocks.at[date, t] for t in holdings)
52
- portfolio_val.append(curr_val)
53
-
54
- # EXIT LOGIC: Sell if score drops or Vol spikes
55
- # In 2008 and 2020, SMA200 cross-under triggered exits
56
- to_sell = [t for t in holdings if stocks.at[date, t] < stocks[t].rolling(200).mean().at[date]]
57
- for t in to_sell:
58
- cash += holdings[t] * stocks.at[date, t]
59
- del holdings[t]
60
 
61
- # ENTRY LOGIC: Combine Stochastics + Sentiment
62
- # Filter for stocks with low Z-score (Value) but positive sentiment (News)
63
- scores = (z_score.loc[date] * -1 * CONFIG["TECH_WEIGHT"]) + (sentiment.loc[date] * CONFIG["FUND_WEIGHT"])
64
- best_picks = scores.sort_values(ascending=False).head(5).index.tolist()
 
 
 
 
 
 
 
 
 
 
65
 
66
- # Allocate
67
- alloc = (curr_val * 0.9) / 5 # Keep 10% cash cushion
68
- for t in best_picks:
69
- if t not in holdings and len(holdings) < 10:
70
- price = stocks.at[date, t]
71
- qty = int(alloc / price)
72
- if cash > qty * price:
73
- holdings[t] = qty
74
- cash -= qty * price
75
-
76
- # 5. Analysis & Visualization
77
- strategy_series = pd.Series(portfolio_val, index=dates[200::5])
78
- bench_series = (benchmark.loc[strategy_series.index] / benchmark.loc[strategy_series.index[0]]) * 100000
79
 
80
- plt.figure(figsize=(12, 8))
81
- plt.subplot(2, 1, 1)
82
- plt.plot(strategy_series, label="Meta-Strategy (Hybrid)", color='blue')
83
- plt.plot(bench_series, label="Nifty 50 (Benchmark)", color='gray', alpha=0.5)
 
 
 
 
 
 
 
 
 
 
 
84
  plt.yscale('log')
85
- plt.title("2004-2026 Simulation: Crisis Performance (2008 & 2020)")
 
86
  plt.legend()
87
-
88
- plt.subplot(2, 1, 2)
89
- drawdown = (strategy_series - strategy_series.cummax()) / strategy_series.cummax()
90
- plt.fill_between(drawdown.index, drawdown, 0, color='red', alpha=0.3)
91
- plt.title("Portfolio Drawdown (Risk Control)")
92
-
93
- plt.tight_layout()
94
- plt.savefig("backtest_result.png")
95
- return "backtest_result.png"
 
6
  import matplotlib.pyplot as plt
7
  import os
8
 
9
+ # --- UNIVERSAL PHYSICS OF FINANCE CONFIG ---
10
  START_DATE = "2004-01-01"
11
+ INITIAL_CAPITAL = 100000
 
 
 
 
 
 
12
 
13
+ def fetch_macro_data():
14
+ """Fetches Equity and Commodity proxies for Crisis Monitoring."""
15
+ assets = {"NIFTY": "^NSEI", "GOLD": "GC=F", "SILVER": "SI=F"}
16
+ data = yf.download(list(assets.values()), start=START_DATE, progress=False)['Close']
17
+ if isinstance(data.columns, pd.MultiIndex):
18
+ data.columns = [col[0] for col in data.columns]
19
+ return data.rename(columns={v: k for k, v in assets.items()}).ffill().bfill()
 
 
 
 
 
 
 
 
20
 
21
+ def run_simulation(data, mode="Renaissance"):
22
+ """
23
+ Modes:
24
+ 'Aggressor' (Momentum-Heavy),
25
+ 'Stoic' (Mean Reversion + Gold),
26
+ 'Renaissance' (Multi-Factor Hybrid)
27
+ """
28
+ df = data.copy()
29
 
30
+ # 1. CORE LOGIC: Universal Factors
31
+ df['SMA_50'] = df['NIFTY'].rolling(50).mean()
32
+ df['SMA_200'] = df['NIFTY'].rolling(200).mean()
33
+ df['VOL'] = df['NIFTY'].pct_change().rolling(20).std()
 
 
 
 
34
 
35
+ # 2. PHASE 1: CRISIS PREDICTOR (Advance-Decline Proxy & Vol Acceleration)
36
+ # Volatility Acceleration: Is 'Fear' growing faster than 'Price'?
37
+ df['VOL_ACCEL'] = df['VOL'].pct_change(5)
38
+ # Crisis Signal: Price below 50SMA + Volatility Spiking
39
+ df['CRISIS_STRESS'] = (df['NIFTY'] < df['SMA_50']) & (df['VOL_ACCEL'] > 0.1)
 
 
 
 
 
 
 
40
 
41
+ # 3. STRATEGY ALLOCATION
42
+ if mode == "Aggressor":
43
+ # 80% Momentum, Fast Exit
44
+ df['Weight_Equity'] = np.where(df['NIFTY'] > df['SMA_50'], 0.8, 0.0)
45
+ elif mode == "Stoic":
46
+ # 60% Mean Reversion, Permanent 30% Gold
47
+ df['Weight_Equity'] = np.where(df['NIFTY'] > df['SMA_200'], 0.6, 0.2)
48
+ df['Weight_Gold'] = 0.3
49
+ else: # Renaissance (The 51% Goal)
50
+ # Dynamic: Full exposure if trend is strong,
51
+ # Aggressive De-leverage if CRISIS_STRESS is True
52
+ df['Weight_Equity'] = np.where(df['CRISIS_STRESS'], 0.0,
53
+ np.where(df['NIFTY'] > df['SMA_200'], 1.0, 0.2))
54
+ df['Weight_Gold'] = np.where(df['CRISIS_STRESS'], 0.5, 0.0)
55
 
56
+ # 4. BACKTEST MATH
57
+ df['Ret_Nifty'] = df['NIFTY'].pct_change()
58
+ df['Ret_Gold'] = df['GOLD'].pct_change()
59
+
60
+ # Strategy Return
61
+ df['Strat_Ret'] = (df['Weight_Equity'].shift(1) * df['Ret_Nifty']) + \
62
+ (df.get('Weight_Gold', 0).shift(1) * df['Ret_Gold'])
63
+
64
+ return INITIAL_CAPITAL * (1 + df['Strat_Ret'].fillna(0)).cumprod()
 
 
 
 
65
 
66
+ def backtest_engine():
67
+ data = fetch_macro_data()
68
+
69
+ # Run all three
70
+ res_agg = run_simulation(data, "Aggressor")
71
+ res_sto = run_simulation(data, "Stoic")
72
+ res_ren = run_simulation(data, "Renaissance")
73
+
74
+ # Plotting
75
+ output_file = "backtest_result.png"
76
+ plt.figure(figsize=(12, 7))
77
+ plt.plot(res_ren, label="The Renaissance (Hybrid - 51% Goal)", color='purple', linewidth=2.5)
78
+ plt.plot(res_agg, label="The Aggressor (Momentum)", color='red', alpha=0.6)
79
+ plt.plot(res_sto, label="The Stoic (Mean Reversion)", color='blue', alpha=0.6)
80
+
81
  plt.yscale('log')
82
+ plt.title("Meta-Simulation (2004-2026): Anticipating 2008 & 2020 Crises")
83
+ plt.ylabel("Wealth (INR)")
84
  plt.legend()
85
+ plt.grid(True, which="both", alpha=0.2)
86
+ plt.savefig(output_file)
87
+ plt.close()
88
+ return output_file