import tushare as ts
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
warnings.filterwarnings("ignore")
# 初始化(替换个人Tushare Token)
ts.set_token("你的Tushare Token")
pro = ts.pro_api()
# 核心参数(8.20-8.29,三次优化版)
START_DATE = "20250820"
END_DATE = "20250829"
# 止盈止损与分时参数
BIG_CANDLE_3PCT = 3 # 3%减仓(含分时)
BIG_CANDLE_5PCT = 5 # 5%清仓(含分时)
STOP_LOSS_DAYS = 2 # 2日内止损
INTRADAY_HOLD_THRESHOLD = 2 # 分时冲高≥2%且收盘≥1%视为有效
# 资金参数(新增北向资金)
INSTITUTION_THRESHOLD = 5 # 机构持股≥5%
FINANCE_INFLOW_2D = 4000 # 连续2日游资净流入≥4000万
NORTH_FUND_INFLOW = 500 # 北向资金当日净流入≥500万元
# 量能与大盘参数
VOL_MULTIPLE = 1.2 # 成交量≥近5日均值1.2倍
HS300_DROP_LIMIT = -1.5 # 沪深300跌幅≤-1.5%暂停买入
# 情绪与热点参数
MARKET_SENTIMENT_THRESHOLD = 50 # 大盘情绪≥50
STOCK_SENTIMENT_THRESHOLD = 60 # 个股舆情≥60
HOT_SECTORS = ["人工智能", "新能源", "半导体"] # 8月核心板块
# 1. 工具函数:动态热点阈值(复用前序逻辑)
def get_dynamic_sector_threshold(industry):
sector_stocks = pro.stock_basic(industry=industry, list_status="L")["ts_code"].head(3)
one_day_pcts = []
for code in sector_stocks:
df = pro.daily(ts_code=code, start_date=END_DATE, end_date=END_DATE)
if not df.empty:
one_day_pcts.append(df["pct_change"].iloc[0])
if not one_day_pcts:
return 5
avg_one_day_pct = np.mean(one_day_pcts)
return 4 if avg_one_day_pct >= 3 else 6 if avg_one_day_pct <= 1 else 5
# 2. 工具函数:个股舆情(复用前序逻辑)
def get_stock_sentiment(ts_code):
try:
sentiment_df = pro.stock_news_sentiment(ts_code=ts_code, start_date=START_DATE, end_date=END_DATE)
return sentiment_df["sentiment_score"].mean() if not sentiment_df.empty else 50
except:
announcements = pro.anns(ts_code=ts_code, start_date=START_DATE, end_date=END_DATE, ann_type="利好")
return 65 if not announcements.empty else 50
# 3. 新增工具函数:分时最高涨幅计算
def get_intraday_high_pct(ts_code, trade_date):
try:
# 获取当日分时数据(按分钟级取最高)
intraday = pro.minute(ts_code=ts_code, date=trade_date.strftime("%Y%m%d"), freq="15min")
if not intraday.empty:
open_price = intraday["open"].iloc[0]
high_price = intraday["high"].max()
return ((high_price - open_price) / open_price) * 100
return 0
except:
# 无分时数据时用日线高低点估算
daily = pro.daily(ts_code=ts_code, start_date=trade_date.strftime("%Y%m%d"), end_date=trade_date.strftime("%Y%m%d"))
if not daily.empty:
return ((daily["high"].iloc[0] - daily["open"].iloc[0]) / daily["open"].iloc[0]) * 100
return 0
# 4. 新增工具函数:北向资金当日净流入
def get_north_fund_inflow(ts_code, trade_date):
try:
north_df = pro.moneyflow_hsgt(trade_date=trade_date.strftime("%Y%m%d"), ts_code=ts_code)
return north_df["north_money"].iloc[0] if not north_df.empty else 0
except:
return 0 # 无北向数据默认0
# 5. 工具函数:板块3日涨幅(复用前序逻辑)
def get_sector_3d_pct(industry):
try:
sector_stocks = pro.stock_basic(industry=industry, list_status="L")["ts_code"].head(5)
pct_sum = 0
count = 0
for code in sector_stocks:
df = pro.daily(ts_code=code, start_date=START_DATE, end_date=END_DATE)
if not df.empty:
pct_sum += df["pct_change"].sum()
count += 1
return pct_sum / count if count > 0 else 0
except:
return 0
# 6. 获取三次优化后个股数据
def get_triple_optimized_data(ts_code, name, industry):
# 基础日线与均线
daily = pro.daily(ts_code=ts_code, start_date=START_DATE, end_date=END_DATE)
if len(daily) < 5:
return None
daily = daily.sort_values("trade_date").reset_index(drop=True)
daily["trade_date"] = pd.to_datetime(daily["trade_date"])
daily["ma5"] = daily["close"].rolling(5).mean()
daily["ma10"] = daily["close"].rolling(10).mean().fillna(daily["close"])
daily["ma20"] = daily["close"].rolling(20).mean().fillna(daily["close"])
daily["daily_pct"] = daily["close"].pct_change() * 100
daily["vol_avg_5d"] = daily["vol"].rolling(5).mean()
# 资金数据(游资+机构+北向)
money = pro.moneyflow_daily(ts_code=ts_code, start_date=START_DATE, end_date=END_DATE)
money["trade_date"] = pd.to_datetime(money["trade_date"])
daily = pd.merge(daily, money[["trade_date", "net_inflow"]], on="trade_date", how="left")
daily["net_inflow"] = daily["net_inflow"].fillna(0)
daily["net_inflow_2d"] = daily["net_inflow"].rolling(2).sum()
# 机构持股
inst = pro.inst_hold(ts_code=ts_code, start_date="20250801", end_date=END_DATE)
daily["inst_hold_ratio"] = inst.sort_values("end_date").iloc[-1]["hold_ratio"] if not inst.empty else 0
# 北向资金(当日净流入)
daily["north_inflow"] = daily.apply(lambda x: get_north_fund_inflow(ts_code, x["trade_date"]), axis=1)
# 热点与情绪(动态热点+个股舆情)
dynamic_thresh = get_dynamic_sector_threshold(industry)
sector_3d_pct = get_sector_3d_pct(industry)
daily["is_hot"] = 1 if (industry in HOT_SECTORS) and (sector_3d_pct >= dynamic_thresh) else 0
daily["stock_sentiment"] = get_stock_sentiment(ts_code)
# 大盘与分时关联(分时最高涨幅)
hs300 = pro.index_daily(ts_code="000300.SH", start_date=START_DATE, end_date=END_DATE)
hs300["trade_date"] = pd.to_datetime(hs300["trade_date"])
daily = pd.merge(daily, hs300[["trade_date", "pct_change"]], on="trade_date", how="left", suffixes=("", "_hs300"))
daily["hs300_pct"] = daily["pct_change_hs300"].fillna(0)
# 分时最高涨幅(用于后续止盈判断)
daily["intraday_high_pct"] = daily.apply(lambda x: get_intraday_high_pct(ts_code, x["trade_date"]), axis=1)
# 个股标签
daily["name"] = name
daily["industry"] = industry
return daily
# 7. 生成三次优化买卖信号(分时过滤+北向资金)
def generate_triple_optimized_signals(df):
if df.empty:
return pd.DataFrame()
# 买入条件(新增北向资金+保留前序优化条件)
df["buy_cond"] = (
# 基础均线
(df["close"] > df["ma5"]) & (df["close"] > df["ma20"]) &
(np.abs((df["close"] - df["ma10"])/df["ma10"]) <= 0.02) &
# 资金三要素(游资+机构+北向)
(df["net_inflow_2d"] >= FINANCE_INFLOW_2D) &
(df["inst_hold_ratio"] >= INSTITUTION_THRESHOLD) &
(df["north_inflow"] >= NORTH_FUND_INFLOW) &
# 量能与大盘
(df["vol"] >= df["vol_avg_5d"] * VOL_MULTIPLE) &
(df["hs300_pct"] >= HS300_DROP_LIMIT) &
# 热点与情绪
(df["is_hot"] == 1) &
(df["market_sentiment"] >= MARKET_SENTIMENT_THRESHOLD) &
(df["stock_sentiment"] >= STOCK_SENTIMENT_THRESHOLD)
)
# 持有周期与分时止盈逻辑
df["buy_date"] = df["trade_date"].where(df["buy_cond"] == 1).fillna(method="ffill")
df["days_since_buy"] = (df["trade_date"] - df["buy_date"]).dt.days
df["valid_hold"] = (df["days_since_buy"] >= 1) & (df["days_since_buy"] <= 5)
# 止盈条件(分时+日线结合)
# 5%清仓:日线达5% 或 分时达5%且收盘≥3%
df["full_sell_cond"] = df["valid_hold"] & (
(df["daily_pct"] >= BIG_CANDLE_5PCT) |
(df["intraday_high_pct"] >= BIG_CANDLE_5PCT) & (df["daily_pct"] >= 3)
)
# 3%减仓:日线达3% 或 分时达3%且收盘≥1%(排除冲高回落)
df["partial_sell_cond"] = df["valid_hold"] & (
(df["daily_pct"] >= BIG_CANDLE_3PCT) |
(df["intraday_high_pct"] >= BIG_CANDLE_3PCT) & (df["daily_pct"] >= INTRADAY_HOLD_THRESHOLD)
)
# 止损条件(保留快速止损)
df["stop_loss_cond"] = df["valid_hold"] & (df["days_since_buy"] <= STOP_LOSS_DAYS) & (df["daily_pct"] <= 0) & (df["close"] < df["ma10"])
# 提取信号(优先级:止损→清仓→减仓)
buy_dates = df[df["buy_cond"]]["trade_date"].unique()
signals = []
for bd in buy_dates:
buy_row = df[df["trade_date"] == bd].iloc[0]
hold_window = df[(df["trade_date"] > bd) & (df["trade_date"] <= bd + timedelta(days=5))]
if hold_window.empty:
continue
# 止损信号
if not hold_window[hold_window["stop_loss_cond"]].empty:
sell_row = hold_window[hold_window["stop_loss_cond"]].iloc[0]
signals.append(build_signal("止损", buy_row, sell_row))
# 清仓信号
elif not hold_window[hold_window["full_sell_cond"]].empty:
sell_row = hold_window[hold_window["full_sell_cond"]].iloc[0]
signals.append(build_signal("5%清仓", buy_row, sell_row))
# 减仓信号
elif not hold_window[hold_window["partial_sell_cond"]].empty:
sell_row = hold_window[hold_window["partial_sell_cond"]].iloc[0]
# 分时减仓按60%仓位计算(比日线减仓高10%,补偿冲高收益)
avg_return = ((sell_row["close"]/buy_row["close"] - 1) * 0.6)
signals.append(build_signal("3%减仓", buy_row, sell_row, avg_return))
return pd.DataFrame(signals)
# 辅助函数:构建信号字典
def build_signal(op_type, buy_row, sell_row, avg_return=None):
base = {
"操作类型": op_type,
"买入日": buy_row["trade_date"].strftime("%Y-%m-%d"),
"代码": buy_row["ts_code"],
"名称": buy_row["name"],
"行业": buy_row["industry"],
"买入价": round(buy_row["close"], 2),
"卖出日": sell_row["trade_date"].strftime("%Y-%m-%d"),
"卖出价": round(sell_row["close"], 2)
}
if avg_return is None:
base["收益率"] = round((sell_row["close"]/buy_row["close"] - 1)*100, 2)
else:
base["收益率"] = round(avg_return*100, 2)
return base
# 8. 执行三次优化回测
def run_triple_optimized_backtest():
# 个股池(排除ST、新股)
stock_pool = pro.stock_basic(
list_status="L",
fields="ts_code,name,industry,list_date"
)
stock_pool = stock_pool[
(~stock_pool["name"].str.contains("ST")) &
(pd.to_datetime(stock_pool["list_date"]) <= pd.to_datetime("20240820"))
]
print(f"8.20-8.29三次优化回测:共{len(stock_pool)}只个股")
all_signals = []
for _, row in stock_pool.iterrows():
df = get_triple_optimized_data(row["ts_code"], row["name"], row["industry"])
if df is None:
continue
sig = generate_triple_optimized_signals(df)
if not sig.empty:
all_signals.append(sig)
return pd.concat(all_signals, ignore_index=True) if all_signals else pd.DataFrame()
# 9. 输出回测结果
if __name__ == "__main__":
result = run_triple_optimized_backtest()
if result.empty:
print("三次优化策略无符合条件信号")
else:
total = len(result)
win_count = len(result[result["收益率"] > 0])
win_rate = (win_count / total) * 100
avg_return = result["收益率"].mean()
loss_return = result[result["收益率"] <= 0]["收益率"].mean()
high_return_count = len(result[result["收益率"] >= 5]) # 高收益(≥5%)信号占比
print(f"\n8.20-8.29三次优化回测结果:")
print(f"- 有效信号数:{total}个")
print(f"- 胜率:{win_rate:.2f}%")
print(f"- 平均收益率:{avg_return:.2f}%")
print(f"- 平均亏损幅度:{loss_return:.2f}%")
print(f"- 高收益信号(≥5%)占比:{round(high_return_count/total*100, 2)}%")
print(f"\n操作类型分布:\n{result['操作类型'].value_counts().to_string()}")
print(f"\n前5个信号详情:")
print(result[["操作类型", "买入日", "代码", "名称", "收益率"]].head().to_string(index=False))