xiaowei-system/scripts/stock_stop_loss_validate.py

181 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
止损机制回测验证 — 无止损 vs 8%止损 vs 5%止损 vs 10%止损
======================================================
在 MA20 策略基础上叠加止损线,对比收益/回撤/胜率。
用法python3 stock_stop_loss_validate.py
"""
import sys, json, urllib.request
from datetime import datetime, timedelta
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).parent))
from stock_ma20_backtest import get_data
OUTPUT = Path.home() / ".hermes" / "stock_backtest"
# 测试股票(白酒下跌趋势最能体现止损价值)
TEST_STOCKS = [
("000858", "五粮液", "白酒"),
("600519", "贵州茅台", "白酒"),
("000568", "泸州老窖", "白酒"),
("300750", "宁德时代", "新能源"),
("002415", "海康威视", "科技"),
]
def backtest_with_stoploss(df, stop_loss_pct=0.0, initial_cash=100000):
"""MA20 + 可选止损回测
stop_loss_pct: 0=无止损0.08=8%止损
"""
if df.empty or len(df) < 30:
return None
d = df.copy()
d["ma20"] = d["close"].rolling(window=20).mean()
d["prev_close"] = d["close"].shift(1)
d["prev_ma20"] = d["ma20"].shift(1)
d["golden_cross"] = (d["prev_close"] < d["prev_ma20"]) & (d["close"] > d["ma20"])
d["death_cross"] = (d["prev_close"] > d["prev_ma20"]) & (d["close"] < d["ma20"])
cash = initial_cash
position = 0
in_position = False
buy_price = 0
buy_date = None
trades = []
equity = []
stop_count = 0
for i, row in d.iterrows():
if pd.isna(row["ma20"]):
continue
price = row["close"]
date_str = row["date"].strftime("%Y-%m-%d")
# 止损检查(持仓时)
if in_position and stop_loss_pct > 0:
dd = (price - buy_price) / buy_price * 100
if dd <= -stop_loss_pct * 100:
cash += position * price
trades.append({"type": "STOP_LOSS", "date": date_str, "price": price,
"shares": position, "profit_pct": dd, "reason": f"止损{dd:.1f}%"})
position = 0
in_position = False
buy_price = 0
stop_count += 1
equity.append({"date": date_str, "value": cash})
continue
# 金叉买入
if row["golden_cross"] and not in_position:
shares = int(cash / price)
if shares > 0:
cash -= shares * price
position = shares
buy_price = price
in_position = True
trades.append({"type": "BUY", "date": date_str, "price": price, "shares": shares})
# 死叉卖出
elif row["death_cross"] and in_position:
cash += position * price
profit_pct = (price - buy_price) / buy_price * 100
trades.append({"type": "SELL", "date": date_str, "price": price,
"shares": position, "profit_pct": profit_pct})
position = 0
in_position = False
buy_price = 0
equity.append({"date": date_str, "value": cash + position * price})
final_price = d.iloc[-1]["close"]
final_value = cash + position * final_price
# 买入持有
bph = d.iloc[19]["close"] if len(d) > 19 else d.iloc[0]["close"]
hold_return = (final_price - bph) / bph * 100 if bph else 0
strat_return = (final_value - initial_cash) / initial_cash * 100
alpha = strat_return - hold_return
# 最大回撤
eq = [e["value"] for e in equity] or [initial_cash]
peak = eq[0]
max_dd = 0
for v in eq:
peak = max(peak, v)
max_dd = max(max_dd, (peak - v) / peak * 100)
# 胜率(含止损单)
exits = [t for t in trades if t["type"] in ("SELL", "STOP_LOSS")]
wins = [t for t in exits if t.get("profit_pct", 0) > 0]
win_rate = len(wins) / len(exits) * 100 if exits else 0
return {
"strat_return": strat_return, "hold_return": hold_return,
"alpha": alpha, "max_dd": max_dd,
"n_trades": len(exits), "win_rate": win_rate,
"stop_count": stop_count, "final_value": final_value,
}
def main():
stop_levels = [0.0, 0.05, 0.08, 0.10]
print("=" * 76)
print("止损机制回测验证 — MA20 + 止损线")
print("=" * 76)
all_results = []
for code, name, industry in TEST_STOCKS:
df = get_data(code, 500)
if df.empty:
print(f" ⚠️ {name} 数据获取失败")
continue
print(f"\n📊 {name}({code}) [{industry}]")
print(f" {'止损线':<8} {'策略收益':>10} {'α':>8} {'最大回撤':>10} {'胜率':>7} {'止损次':>6}")
print(f" {'-'*52}")
stock_results = {}
for sl in stop_levels:
r = backtest_with_stoploss(df, stop_loss_pct=sl)
if not r:
continue
stock_results[f"{sl:.0%}"] = r
label = f"{sl:.0%}" if sl > 0 else "无止损"
print(f" {label:<8} {r['strat_return']:>+9.1f}% {r['alpha']:>+7.1f}% "
f"{r['max_dd']:>9.1f}% {r['win_rate']:>6.1f}% {r['stop_count']:>6}")
all_results.append({"code": code, "name": name, "industry": industry, "results": stock_results})
# 汇总8%止损 vs 无止损
print("\n" + "=" * 76)
print("📈 汇总8%止损 vs 无止损)")
print("=" * 76)
alphas_none, alphas_8, dd_none, dd_8 = [], [], [], []
for r in all_results:
if "0%" in r["results"] and "8%" in r["results"]:
alphas_none.append(r["results"]["0%"]["alpha"])
alphas_8.append(r["results"]["8%"]["alpha"])
dd_none.append(r["results"]["0%"]["max_dd"])
dd_8.append(r["results"]["8%"]["max_dd"])
if alphas_none:
print(f" 平均 α: 无止损 {np.mean(alphas_none):+.2f}% → 8%止损 {np.mean(alphas_8):+.2f}% "
f"(差 {np.mean(alphas_8)-np.mean(alphas_none):+.2f}%)")
print(f" 平均回撤: 无止损 {np.mean(dd_none):.2f}% → 8%止损 {np.mean(dd_8):.2f}% "
f"(差 {np.mean(dd_8)-np.mean(dd_none):+.2f}%)")
print(f"\n 结论: {'✅ 8%止损有效(α↑或回撤↓)' if np.mean(dd_8) < np.mean(dd_none) else '⚠️ 止损收益影响需权衡'}")
out = OUTPUT / "stoploss_validation.json"
with open(out, "w", encoding="utf-8") as f:
json.dump({"generated": datetime.now().strftime("%Y-%m-%d %H:%M"),
"results": all_results}, f, ensure_ascii=False, indent=2, default=str)
print(f"\n📁 已保存: {out}")
if __name__ == "__main__":
main()