File size: 7,769 Bytes
0581467 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | # backend/simulation/simulate_nvda.py
"""
Dedicated Ticker Simulation: NVDA (Pop & Fade / Mean-Reversion Specialist)
Zero-overlap independent simulation for NVDA.
Focus:
- Specifically addresses the "先涨后跌" (Morning Pop then Afternoon Bleed) phenomenon.
- Morning Momentum Scalp (09:30 - 10:15): Quick profit taking near morning resistance.
- Distribution Transition: When peak gains are rejected and price drops below VWAP, long entries are banned.
- Fade / Shorting the post-pop decay back to daily mean.
"""
import sys
import os
import yfinance as yf
import pandas as pd
import numpy as np
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
from backend.app.ml.ticker_regime_engine import TickerRegimeEngine, MarketRegime
def run_nvda_simulation(period="5d", interval="5m", initial_capital=100000.0):
print("=" * 80)
print(" 【NVDA 独立专用量化回测与模拟系统 (DEDICATED SIMULATION: NVDA)】")
print(" 核心特征: 机构高流动性洗盘、'先涨后跌' 冲高回落、严禁高位死拿、破位止盈反手")
print(f" 初始本金: ${initial_capital:,.2f} | 周期: {period} (Interval: {interval})")
print("=" * 80 + "\n")
df = yf.download("NVDA", period=period, interval=interval, progress=False)
if df.empty:
print("Error: Could not retrieve NVDA data.")
return
if isinstance(df.columns, pd.MultiIndex):
df.columns = [col[0] for col in df.columns]
close_s = df["Close"].squeeze()
open_s = df["Open"].squeeze()
high_s = df["High"].squeeze()
low_s = df["Low"].squeeze()
vol_s = df["Volume"].squeeze()
df["VWAP"] = TickerRegimeEngine.calculate_vwap(df)
df["EMA_9"] = close_s.ewm(span=9, adjust=False).mean()
df["EMA_21"] = close_s.ewm(span=21, adjust=False).mean()
capital = initial_capital
position_shares = 0
position_side = None # "LONG" or "SHORT"
entry_price = 0.0
entry_time = None
trades = []
extreme_price = 0.0
for i in range(20, len(df)):
sub_df = df.iloc[:i+1]
regime_info = TickerRegimeEngine.analyze_ticker_regime(sub_df, "NVDA")
current_time = df.index[i]
time_str = str(current_time)
bar_close = float(close_s.iloc[i])
bar_high = float(high_s.iloc[i])
bar_low = float(low_s.iloc[i])
bar_vwap = float(df["VWAP"].iloc[i])
bar_ema9 = float(df["EMA_9"].iloc[i])
bar_ema21 = float(df["EMA_21"].iloc[i])
is_morning = any(t in time_str for t in ["09:35", "09:40", "09:45", "09:50", "09:55", "10:00", "10:05", "10:10"])
is_eod = "15:50" in time_str or "15:55" in time_str
# 1. Manage Active Position
if position_shares > 0:
if position_side == "LONG":
extreme_price = max(extreme_price, bar_high)
profit_pct = (bar_close - entry_price) / entry_price * 100.0
# NVDA Long Rule: Never hold into afternoon decay.
# If profit > 1.0% or price crosses below VWAP or 10:30 arrives -> EXIT
is_take_profit = profit_pct >= 1.2
is_vwap_lost = bar_close < bar_vwap
is_morning_ended = "10:30" in time_str or "10:45" in time_str
if is_take_profit or is_vwap_lost or is_morning_ended or is_eod:
exit_price = bar_close
pnl = position_shares * (exit_price - entry_price)
capital += pnl
trades.append({
"entry_time": entry_time,
"exit_time": current_time,
"side": "LONG",
"shares": position_shares,
"entry_price": entry_price,
"exit_price": exit_price,
"pnl_$": pnl,
"return_%": (exit_price - entry_price) / entry_price * 100.0,
"reason": "Quick Pop TP" if is_take_profit else ("VWAP Exit Before Fade" if is_vwap_lost else "Morning Window Close")
})
position_shares = 0
position_side = None
elif position_side == "SHORT":
extreme_price = min(extreme_price, bar_low)
profit_pct = (entry_price - bar_close) / entry_price * 100.0
# Short Fade logic: Cover when VWAP is reclaimed or EOD
if bar_close > bar_vwap * 1.003 or is_eod or profit_pct >= 1.5:
exit_price = bar_close
pnl = position_shares * (entry_price - exit_price)
capital += pnl
trades.append({
"entry_time": entry_time,
"exit_time": current_time,
"side": "SHORT",
"shares": position_shares,
"entry_price": entry_price,
"exit_price": exit_price,
"pnl_$": pnl,
"return_%": (entry_price - exit_price) / entry_price * 100.0,
"reason": "EOD Flatten" if is_eod else "Short Fade Target"
})
position_shares = 0
position_side = None
# 2. Entry Logic
elif position_shares == 0 and not is_eod:
# Case A: Morning Open Spike (09:35 - 10:00) with price above VWAP -> Quick Long Scalp
if is_morning and bar_close > bar_vwap and bar_close > bar_ema9:
alloc = capital * 0.70
shares = int(alloc / bar_close)
if shares > 0:
position_shares = shares
position_side = "LONG"
entry_price = bar_close
entry_time = current_time
extreme_price = bar_close
# Case B: POP_AND_FADE detected (Failed peak, under VWAP) -> Fade Short
elif regime_info.regime == MarketRegime.POP_AND_FADE and bar_close < bar_vwap:
alloc = capital * 0.70
shares = int(alloc / bar_close)
if shares > 0:
position_shares = shares
position_side = "SHORT"
entry_price = bar_close
entry_time = current_time
extreme_price = bar_close
df_trades = pd.DataFrame(trades)
print("=" * 80)
print(" 【NVDA 独立交易明细 (INDIVIDUAL TRADE LOG)】")
print("=" * 80)
if not df_trades.empty:
for idx, row in df_trades.iterrows():
win_icon = "🟢 盈利" if row["pnl_$"] > 0 else "🔴 亏损"
print(f" Trade #{idx+1} | {row['entry_time']} -> {row['exit_time']} | "
f"{row['side']:5s} @ ${row['entry_price']:.2f} -> ${row['exit_price']:.2f} | "
f"PnL: ${row['pnl_$']:+,.2f} ({row['return_%']:+.2f}%) | {win_icon} ({row['reason']})")
else:
print(" No trades executed.")
total_pnl = capital - initial_capital
total_ret_pct = total_pnl / initial_capital * 100.0
win_rate = (df_trades["pnl_$"] > 0).mean() * 100.0 if not df_trades.empty else 0.0
print("\n" + "=" * 80)
print(" 【NVDA 独立回测指标总结】")
print("=" * 80)
print(f" • 期末总资产 (Ending Equity): ${capital:,.2f}")
print(f" • 累计总净利润 (Net Total PnL): ${total_pnl:+,.2f}")
print(f" • 总收益率 (Total Net Return): {total_ret_pct:+.2f}%")
print(f" • 胜率 (Win Rate): {win_rate:.1f}% ({len(df_trades)} 笔交易)")
print("=" * 80 + "\n")
if __name__ == "__main__":
run_nvda_simulation()
|