炒股python
AI 生成买股票的“策略设计”可以像你做软件架构一样,先分层、再逐步细化。。
·
AI 生成
ps:本人只是把csdn当成自己的草稿本在使用,偶尔发的文章是别人可以直接使用的成品,但大多数时候是这种零散的资料收集。这种你们就不用看了
买股票的“策略设计”可以像你做软件架构一样,先分层、再逐步细化。一个完整的买入策略通常包含四个核心部分:目标设定 → 策略框架 → 执行机制 → 风险控制。
1. 明确目标
在设计策略前,你要先回答:
- 你的目标是 长期增值(类似买指数基金,3-5年以上持有),还是 短期套利(几天到几周的机会)?
- 你愿意承受多大的波动?(10%?30%?)
- 你期望的年化回报是多少?
这一步类似于“需求分析”,决定了后面所有的设计。
2. 策略框架
可以从几个维度来组合:
(1)基本面驱动
- 场景:适合长期投资。
- 方法:关注公司盈利能力(ROE、利润增长)、行业位置、护城河。
- 案例:如果你相信 AI 发展趋势,就挑行业龙头并长期持有。
(2)技术面驱动
- 场景:适合短期或波段操作。
- 方法:用均线、成交量、形态来判断买点和卖点。
- 案例:突破60日均线时买入,跌破时卖出。
(3)事件驱动
- 场景:中短期机会。
- 方法:关注财报、政策、并购消息。
- 案例:新能源补贴政策发布 → 买入龙头。
(4)量化/程序化
- 场景:更偏专业。
- 方法:用算法(趋势跟随、均值回归、因子模型)筛选买点。
- 案例:设置 RSI < 30 自动买入,>70 自动卖出。
3. 执行机制
- 买入条件:明确触发点(例如“PE < 20 且营收增长 > 15%” 或 “MACD 金叉 + 放量”)。
- 仓位管理:一次买满还是分批买?通常建议分批买入。
- 卖出条件:必须事先设定(止损线,比如-10%;止盈线,比如+30%)。
4. 风险控制
就像软件容错一样,风险管理比“选哪只股票”更关键:
- 单只股票仓位不超过总资金的20%。
- 设置止损(亏损到某个程度自动卖出)。
- 行业/资产分散:不要把钱全压在一个行业或国家。
5. 一个例子(技术+基本面混合)
- 先筛选:年营收增速 > 20%、市盈率合理(<30)、行业趋势向上。
- 买点:股价突破 120 日均线,并且成交量 > 20 日平均成交量。
- 仓位:总资金的 30%,分三次买入。
- 止损:跌破 120 日均线 5% 卖出。
- 止盈:上涨 30% 卖一半,剩下长期持有。
要不要我帮你设计一个 可执行的示例策略(含条件、买卖点、仓位分配),然后给你做成像伪代码/流程图那样的结构?这样你可以像跑程序一样“回测”这个策略。
太好了,我们把策略落地成 Python,并顺手给你一套可直接回测的脚本和示例数据。
我已在你的会话里生成了这些文件(可直接下载):
- 下载脚本:ma_breakout_backtest.py
- 下载示例数据:synthetic_TEST.csv
- 示例回测图:equity_curve_synthetic.png
脚本内容要点(默认可改的超参见脚本顶部 Params 和命令行参数):
- 买点:收盘价上穿并站上 SMA(120),且当日成交量 > 20 日均量,且之前 10 天一直在均线下方(避免假突破)。
- 加仓:分三笔各 1/3 仓位:信号日入第一笔;价格较第一笔涨 +3% 入第二笔;涨 +6% 入第三笔。
- 卖点/风控:收盘价跌破 SMA120 的 5% 缓冲 或 跌破 持仓均价的 10%(硬止损)即清仓;+30% 先止盈一半,剩余靠卖点退出。
- 交易成本:默认双向各 5 bps 手续费和 5 bps 滑点(可改)。
- 输出:终端打印收益统计;可绘制策略净值 vs. 买入并持有。
如何本地跑历史数据
- 用你自己的 CSV(字段:
Date,Open,High,Low,Close,Volume):
python ma_breakout_backtest.py --csv your_stock.csv --ticker YOURTICK --plot
- 若本地能联网,可用 yfinance 抓数据:
pip install yfinance
python ma_breakout_backtest.py --ticker AAPL --start 2015-01-01 --end 2025-09-01 --plot
- 直接跑示例合成数据:
python ma_breakout_backtest.py --csv synthetic_TEST.csv --ticker TEST --plot
常用参数调优(命令行)
- 均线与量能窗口:
--sma_len 120 --avgvol_len 20 - 假突破过滤:
--lookback_below 10 - 加仓阈值:
--add1 0.03 --add2 0.06 - 卖点:
--sma_exit_buf 0.05 --hard_stop 0.10 --tp 0.30 - 成本:
--comm_bps 5 --slip_bps 5 - 初始资金:
--initial 100000
接下来可以怎么迭代(前瞻性建议)
- 多标的 & 资金分配:把脚本扩展为投资组合回测(多只票+等权或波动率预算)。
- 更稳健的入场:加入 回撤后再突破 或 突破后 N 日内不回落 的确认条件。
- 风控替代:尝试 ATR 跟踪止损 或 移动均线止盈,对比回撤与胜率。
- 参数鲁棒性:做 参数网格搜索 与 滚动/步进回测(walk-forward),避免过拟合。
- 交易成本敏感性:高/低成本场景下对比净值和夏普,评估策略在真实市场的可行性。
- 事件过滤:财报日前后避开或减仓,看看对回撤的改善。
如果你愿意,我可以把脚本升级为多标的组合回测版本,或加上参数网格搜索与性能热力图导出,方便你系统调参。
#!/usr/bin/env python3
"""
MA Breakout Backtester (single-stock)
-------------------------------------
Strategy (default parameters, configurable via CLI):
- Entry: Close crosses and *closes above* SMA(120) AND Volume > AvgVolume(20),
and price has been below SMA for at least `lookback_below` days prior (default 10).
- Position sizing: 3 tranches (each ~1/3 of target position).
* 1st tranche on entry signal day at close.
* 2nd tranche when price is +3% above first entry price.
* 3rd tranche when price is +6% above first entry price.
- Risk controls:
* Exit if Close < SMA(120)*(1 - sma_exit_buf) OR
Close < avg_entry_price * (1 - hard_stop), whichever triggers first.
* Take-profit: when the position PnL reaches +30%, sell 1/2; keep the rest until exit.
- Transaction costs:
* Commission in bps (default 5 bps = 0.05% per trade).
* Slippage in bps (default 5 bps) applied to fills.
Data sources:
- Preferred: CSV with columns: Date, Open, High, Low, Close, Volume (case-insensitive), Date parseable.
- Optional: yfinance (if internet available).
Usage examples:
# Load from CSV
python ma_breakout_backtest.py --csv mydata.csv --ticker TEST
# Fetch from yfinance (needs internet)
python ma_breakout_backtest.py --ticker AAPL --start 2015-01-01 --end 2025-09-01
Outputs:
- Summary metrics printed to stdout
- Optional: save equity curve to PNG
Author: ChatGPT (GPT-5 Thinking)
"""
import argparse
import sys
import math
from dataclasses import dataclass
from typing import Optional, Tuple, List
import numpy as np
import pandas as pd
# yfinance is optional; only import if requested
def _try_import_yf():
try:
import yfinance as yf
return yf
except Exception:
return None
@dataclass
class Params:
sma_len: int = 120
avgvol_len: int = 20
lookback_below: int = 10
add1_level: float = 0.03 # +3% for 2nd tranche
add2_level: float = 0.06 # +6% for 3rd tranche
sma_exit_buf: float = 0.05 # 5% below SMA exit buffer
hard_stop: float = 0.10 # 10% hard stop from avg entry
take_profit: float = 0.30 # take half at +30%
commission_bps: float = 5.0
slippage_bps: float = 5.0
initial_capital: float = 100000.0
risk_per_trade: float = 1.0 # % of equity to target per new position (not enforced tightly in tranche model)
def load_data_from_csv(path: str) -> pd.DataFrame:
df = pd.read_csv(path)
cols = {c.lower(): c for c in df.columns}
required = ['date','open','high','low','close','volume']
for k in required:
if k not in cols:
raise ValueError(f"CSV missing column: {k}")
df['Date'] = pd.to_datetime(df[cols['date']])
df = df.rename(columns={
cols['open']: 'Open', cols['high']:'High', cols['low']:'Low',
cols['close']:'Close', cols['volume']:'Volume'
})[['Date','Open','High','Low','Close','Volume']].sort_values('Date').reset_index(drop=True)
return df
def load_data_yf(ticker: str, start: Optional[str], end: Optional[str]) -> pd.DataFrame:
yf = _try_import_yf()
if yf is None:
raise RuntimeError("yfinance not installed or import failed. Install with: pip install yfinance")
data = yf.download(ticker, start=start, end=end, auto_adjust=True, progress=False)
if data is None or data.empty:
raise RuntimeError("No data returned from yfinance.")
data = data.reset_index()
# yfinance columns: Date, Open, High, Low, Close, Adj Close, Volume
df = data[['Date','Open','High','Low','Close','Volume']].copy()
df = df.sort_values('Date').reset_index(drop=True)
return df
def prepare_indicators(df: pd.DataFrame, p: Params) -> pd.DataFrame:
df = df.copy()
df['SMA'] = df['Close'].rolling(p.sma_len).mean()
df['AvgVol'] = df['Volume'].rolling(p.avgvol_len).mean()
# track whether price has been below SMA for lookback days prior
below = df['Close'] < df['SMA']
df['BelowLookback'] = below.rolling(p.lookback_below, min_periods=p.lookback_below).apply(lambda x: float(all(x)), raw=False).fillna(0.0) > 0.5
return df
@dataclass
class PositionState:
qty: float = 0.0
avg_price: float = 0.0
first_entry_price: float = 0.0
added_add1: bool = False
added_add2: bool = False
realized_pnl: float = 0.0
def _apply_tc(price: float, commission_bps: float, slippage_bps: float, side: int) -> float:
# side: +1 buy, -1 sell
price_slip = price * (1 + slippage_bps/10000.0 * side)
commission = price_slip * (commission_bps/10000.0)
return price_slip + commission * side
def backtest(df: pd.DataFrame, p: Params, ticker: str = "TICK") -> Tuple[pd.DataFrame, dict]:
df = prepare_indicators(df, p)
equity = p.initial_capital
pos = PositionState()
equity_curve = []
trades: List[dict] = []
for i, row in df.iterrows():
date = row['Date']
close = float(row['Close'])
sma = float(row['SMA']) if not math.isnan(row['SMA']) else None
avgvol = float(row['AvgVol']) if not math.isnan(row['AvgVol']) else None
below_lb = bool(row['BelowLookback'])
# Mark-to-market
m2m = pos.qty * close
equity_now = equity + m2m
equity_curve.append({'Date': date, 'Equity': equity_now, 'Close': close})
# Skip until indicators ready
if sma is None or avgvol is None:
continue
# Generate signals
entry_signal = (close > sma) and (row['Volume'] > avgvol) and below_lb
exit_sma = close < sma * (1 - p.sma_exit_buf)
exit_hard = (pos.qty > 0) and (close < pos.avg_price * (1 - p.hard_stop))
take_half = (pos.qty > 0) and ((close - pos.avg_price)/pos.avg_price >= p.take_profit)
# Position sizing target: use full equity, but we add in tranches.
# For illustration, we'll size such that full position is 90% of equity (aggressive),
# but tranches keep the staged entry behavior.
target_position_value = equity_now * 0.9
# ENTRY
if entry_signal and pos.qty == 0:
# First tranche at today's close (with TC)
fill_px = _apply_tc(close, p.commission_bps, p.slippage_bps, side=+1)
tranche_value = target_position_value / 3.0
qty = tranche_value / fill_px
cost = qty * fill_px
pos.qty += qty
pos.avg_price = fill_px
pos.first_entry_price = fill_px
equity -= cost # cash out
trades.append({'Date': date, 'Action': 'BUY_1', 'Price': fill_px, 'Qty': qty})
elif pos.qty > 0:
# ADD-ON 1
if (not pos.added_add1) and (close >= pos.first_entry_price * (1 + p.add1_level)):
fill_px = _apply_tc(close, p.commission_bps, p.slippage_bps, side=+1)
tranche_value = target_position_value / 3.0
qty = tranche_value / fill_px
cost = qty * fill_px
pos.avg_price = (pos.avg_price * pos.qty + fill_px * qty) / (pos.qty + qty)
pos.qty += qty
equity -= cost
pos.added_add1 = True
trades.append({'Date': date, 'Action': 'BUY_2', 'Price': fill_px, 'Qty': qty})
# ADD-ON 2
if (not pos.added_add2) and (close >= pos.first_entry_price * (1 + p.add2_level)):
fill_px = _apply_tc(close, p.commission_bps, p.slippage_bps, side=+1)
tranche_value = target_position_value / 3.0
qty = tranche_value / fill_px
cost = qty * fill_px
pos.avg_price = (pos.avg_price * pos.qty + fill_px * qty) / (pos.qty + qty)
pos.qty += qty
equity -= cost
pos.added_add2 = True
trades.append({'Date': date, 'Action': 'BUY_3', 'Price': fill_px, 'Qty': qty})
# EXIT logic (priority: take-profit partial, then full exits)
if pos.qty > 0:
# Take-profit: sell half if threshold reached (only once)
if take_half and not any(t['Action']=='TP_HALF' for t in trades if t['Date']==date):
sell_qty = pos.qty * 0.5
fill_px = _apply_tc(close, p.commission_bps, p.slippage_bps, side=-1)
proceeds = sell_qty * fill_px
equity += proceeds
pos.qty -= sell_qty
# avg_price remains the same for remaining position
trades.append({'Date': date, 'Action': 'TP_HALF', 'Price': fill_px, 'Qty': sell_qty})
# Full exit conditions
if (close < sma * (1 - p.sma_exit_buf)) or (close < pos.avg_price * (1 - p.hard_stop)):
fill_px = _apply_tc(close, p.commission_bps, p.slippage_bps, side=-1)
proceeds = pos.qty * fill_px
equity += proceeds
trades.append({'Date': date, 'Action': 'EXIT', 'Price': fill_px, 'Qty': pos.qty})
pos.qty = 0.0
pos.avg_price = 0.0
pos.first_entry_price = 0.0
pos.added_add1 = False
pos.added_add2 = False
# Final mark-to-market
if pos.qty > 0:
equity_now = equity + pos.qty * df.iloc[-1]['Close']
else:
equity_now = equity
ec = pd.DataFrame(equity_curve).dropna()
ec['Returns'] = ec['Equity'].pct_change().fillna(0.0)
# Buy & Hold benchmark
close = df['Close'].values
if len(close) > 0:
bh_returns = np.zeros_like(close, dtype=float)
bh_returns[1:] = close[1:] / close[0] - 1.0
ec['BH'] = (1 + pd.Series(np.r_[0, np.diff(close)/close[:-1]], index=ec.index)).cumprod() * (p.initial_capital)
# align BH to initial capital for visual comparison
else:
ec['BH'] = np.nan
stats = compute_stats(ec['Equity'].values, p.initial_capital, ec['Returns'].values)
return ec, stats
def compute_stats(equity_curve: np.ndarray, initial: float, daily_returns: np.ndarray) -> dict:
total_return = equity_curve[-1] / initial - 1.0
# CAGR
n = len(equity_curve)
if n <= 1:
cagr = 0.0
else:
years = n / 252.0
cagr = (equity_curve[-1]/initial) ** (1/years) - 1 if years > 0 else 0.0
# Max Drawdown
peaks = np.maximum.accumulate(equity_curve)
drawdowns = equity_curve / peaks - 1.0
max_dd = drawdowns.min() if len(drawdowns) else 0.0
# Sharpe (daily, risk-free ~ 0)
if daily_returns.std() > 0:
sharpe = daily_returns.mean() / daily_returns.std() * np.sqrt(252)
else:
sharpe = 0.0
return {
'Total Return': total_return,
'CAGR': cagr,
'Max Drawdown': max_dd,
'Sharpe': sharpe,
'Last Equity': float(equity_curve[-1]) if len(equity_curve) else initial,
'Bars': int(n)
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--ticker', type=str, default='TICK')
ap.add_argument('--csv', type=str, default=None, help='Path to CSV with Date,Open,High,Low,Close,Volume')
ap.add_argument('--start', type=str, default=None)
ap.add_argument('--end', type=str, default=None)
ap.add_argument('--plot', action='store_true')
ap.add_argument('--save_plot', type=str, default=None)
# Hyperparameters
ap.add_argument('--sma_len', type=int, default=120)
ap.add_argument('--avgvol_len', type=int, default=20)
ap.add_argument('--lookback_below', type=int, default=10)
ap.add_argument('--add1', type=float, default=0.03)
ap.add_argument('--add2', type=float, default=0.06)
ap.add_argument('--sma_exit_buf', type=float, default=0.05)
ap.add_argument('--hard_stop', type=float, default=0.10)
ap.add_argument('--tp', type=float, default=0.30)
ap.add_argument('--comm_bps', type=float, default=5.0)
ap.add_argument('--slip_bps', type=float, default=5.0)
ap.add_argument('--initial', type=float, default=100000.0)
args = ap.parse_args()
p = Params(
sma_len=args.sma_len,
avgvol_len=args.avgvol_len,
lookback_below=args.lookback_below,
add1_level=args.add1,
add2_level=args.add2,
sma_exit_buf=args.sma_exit_buf,
hard_stop=args.hard_stop,
take_profit=args.tp,
commission_bps=args.comm_bps,
slippage_bps=args.slip_bps,
initial_capital=args.initial,
)
if args.csv:
df = load_data_from_csv(args.csv)
else:
df = load_data_yf(args.ticker, args.start, args.end)
ec, stats = backtest(df, p, ticker=args.ticker)
print("=== Summary ===")
for k,v in stats.items():
if isinstance(v, float):
print(f"{k:>15}: {v:.4f}")
else:
print(f"{k:>15}: {v}")
if args.plot or args.save_plot:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(ec['Date'], ec['Equity'], label='Strategy')
plt.plot(ec['Date'], ec['BH'], label='Buy & Hold')
plt.legend()
plt.title(f"Equity Curve: {args.ticker}")
plt.xlabel("Date")
plt.ylabel("Equity")
if args.save_plot:
plt.savefig(args.save_plot, dpi=150, bbox_inches='tight')
if args.plot:
plt.show()
if __name__ == "__main__":
main()
A 股的数据获取和美股不一样,yfinance 在国内基本没法直接拿。主流有两条路:
1. 免费数据源(Python 库)
🔹 Tushare
- 特点:A 股最常用的数据接口,日线、分钟线、财报都有。
- 限制:需要申请 token(免费用户每天大概 200 次调用,够做日线回测)。
- 安装 & 用法:
pip install tushare
import tushare as ts
import pandas as pd
# 用你的 token 初始化
ts.set_token("your_token_here")
pro = ts.pro_api()
# 拉取日线数据
df = pro.daily(ts_code='600519.SH', start_date='20150101', end_date='20250901')
df = df.rename(columns={
'trade_date':'Date', 'open':'Open', 'high':'High',
'low':'Low', 'close':'Close', 'vol':'Volume'
})
df['Date'] = pd.to_datetime(df['Date'])
df = df.sort_values('Date')
df.to_csv("600519.csv", index=False)
然后就能直接喂给我给你的回测脚本 ma_breakout_backtest.py。
🔹 AkShare
- 特点:完全免费,不用注册,数据接口多。
- 安装 & 用法:
pip install akshare
import akshare as ak
# 获取贵州茅台的日线
df = ak.stock_zh_a_daily(symbol="sh600519", adjust="qfq")
df = df.reset_index()
df = df.rename(columns={
'date':'Date','open':'Open','high':'High',
'low':'Low','close':'Close','volume':'Volume'
})
df.to_csv("600519.csv", index=False)
2. 本地化使用
获取到 csv 以后,你就能这样跑:
python ma_breakout_backtest.py --csv 600519.csv --ticker 600519 --plot
3. 前瞻性建议
- 分钟线回测:Tushare/AkShare 都有分钟级别数据,你可以把策略下探到更高频。
- 多标的批量测试:把一篮子 A 股股票下载下来,统一丢进脚本,测试组合收益。
- 财报/因子结合:A 股对政策、财务因子敏感,可以在买点前加个“基本面过滤”。
要不要我帮你改一版 支持直接调用 Tushare 或 AkShare 的 backtest 脚本,这样你连 CSV 都不用存,每次调用就能自动取数据跑回测?
更多推荐

所有评论(0)