Spaces:
Sleeping
Sleeping
Update backtest.py
Browse files- 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 |
-
# ---
|
| 10 |
START_DATE = "2004-01-01"
|
| 11 |
-
|
| 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
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 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 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
#
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
# 4. Simulation Loop (Handling 2008 & 2020)
|
| 44 |
-
portfolio_val = [100000]
|
| 45 |
-
cash = 100000
|
| 46 |
-
holdings = {}
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 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 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 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 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
plt.yscale('log')
|
| 85 |
-
plt.title("2004-2026
|
|
|
|
| 86 |
plt.legend()
|
| 87 |
-
|
| 88 |
-
plt.
|
| 89 |
-
|
| 90 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|