北炒是游资中打板一族的一个另类,他总是以打炸板股出名,今天给大家把回测代码也分享了,可以自己去调试优化,如果能卖在早盘10点前高点的80%的话,该策略胜率达到60%,盈亏比是1.54,3%的盈利占比达到26.13%。当然人家不是所有炸板股都买的,有自己的判断,我这里是假设对所有炸板回封股都买的结果。比如北炒在23-8-17日买入重庆啤酒的买点:
% q+ W' A* T0 ^" Q$ X3 X* ?# r0 `0 u& ?! S% I$ P5 s8 s
; E4 W8 X3 c; J9 k$ R这是他在23-8-18的交易记录:
2 z9 y! I" t) s: A; E/ w# H4 |7 N8 B. V
& B) Q3 E1 G( [" s1 @) b; C4 l这是对所有主板股票回测的代码分享:
$ L% @) L) K) i- f0 } 需要注意的是,上述策略的回测过程未计算滑点对策略收益产生的影响。且过去的表现并不能保证未来的表现,市场风险和不确定性永远存在。后面我会根据自己优化后的策略,用5万元开展实盘来验证这个策略的价值,欢迎大家关注,如果持仓标的发生变动会给大家更新。import csv
5 P9 w5 d- N4 t% U* ?7 Simport os, q6 D' e$ j J( G
import time- E( ^% s' j; N0 j) p( c
1 b$ }' u8 E( P$ i; rimport backtrader as bt; B5 |% R* r( P- C. W
import numpy as np. z* ^- e4 `6 a5 [8 E2 V; B* N7 z0 l
import pandas as pd
* v/ D9 f5 |1 O5 A' I; a: d
) i, E1 {' n6 \4 Gfrom SelfTrade import SelfTrade
4 C# ?" M" ^0 j7 @import datetime
X: T( Q0 z h t
$ h5 C1 l5 t9 E2 Y'''炸首板再次打板策略3 s& b# x) f9 n7 D
1. 当日涨停过,又炸板了,再次快要涨停时候买入(可以尝试炸首板,或者近期涨幅在一定范围内的炸板)' M# Q& W( Z' _: g) S9 Y8 X
2。第二日早上冲高回落卖出,下跌反弹后卖出,一般10点前挑高点卖出,如果不涨停的话% ?$ l8 V T V [1 e
'''
7 s+ W6 O5 L9 b$ Yclass ZhaBanSelect(bt.Strategy):; }5 y9 x4 d. N$ G. G
def log(self, Type, Code, PriceType, Price, Date):
4 m# L& L( v2 X; y; }- `5 W/ z( N, p ''' 策略的日志函数'''
; Y' I; [& {* m3 ^/ e with open(self.logFile, 'a', newline='', encoding='utf-8') as file:
: F3 g+ Z0 B9 f0 H6 s- l( s) S writer = csv.writer(file)9 B8 F+ f8 o3 s. |8 o
writer.writerow([Type, Code, PriceType, Price, Date])
8 W4 {) s: v A( \: x print('%s, %s, %s, %s, %s' % (Type, Code, PriceType, Price, Date))
. S) B4 f0 E& o3 G9 L) ^9 u9 `) b: E9 f
params = dict(* ~( a: _' ?) X: ]/ R0 L, {: x
hostDays=1, # 持有最大天数
, O+ j& ?; I8 i8 N8 v UP_LIMIT = 0.099, # 涨停标准
, Z8 u* w8 n B. H* X UP_LIMIT_BUY=1.0981, # 买入起始价位
: N& C7 u. x9 L6 ^" _ START_TIME='9:30:00',
- e# p" P1 j6 W( j+ g END_TIME='10:00:00',
% i u. O2 n. y }! x3 Q& L Min_FOLDER=r'/Users/Downloads/tdx/min',
& i$ ^7 P1 Z0 o% t6 E- B, Y9 x
- I( X" [/ E, D( k # 波动低
' J9 I, p* A6 R! z: q5 e9 H# w% O0 B LOW_WAVE_RATE=0.3, # 波动幅度$ y* [0 P' Q, K# l% q( g
LOW_WAVE_PERIOD=30,
x ?2 O2 V; }- [/ C )) h7 z8 Z3 H% \, p8 z6 c. K2 h
3 {3 P) Y7 `+ O/ u
def __init__(self):: D* b% e- A8 ]9 F3 |6 U3 v7 ^
self.start_dates = {}: h6 z0 P$ t$ R2 g5 x
self.start_dates_flag = {} n, Q- ~3 N5 G- `" V# x% n; }+ Q
self.diff_start_dates_days = {}
+ I6 ^0 K0 T: t; x* B0 R self.selfTrader = SelfTrade(commission_rate=0.0007, cash=10000000), D9 u1 C" T$ M h
self.candidates1 = {} # 进入第一波候选观察股池
- c; n( N/ I' i. ?. c self.positionsMy = {} # 持仓股票及其买入价格
/ N8 u0 D2 S$ F& m9 t9 A( h% ]" _ self.days_since_in_host= {} # 记录每只股票持有后
. W2 ]; r, T; D9 P2 d4 U+ X self.days_since_in_pool= {}
- s8 W* @3 A$ A& R+ ^, Z0 r- ?- g self.inds = dict() l! H( K$ y+ ]$ g7 a1 Z0 I+ j* r& X
self.first_day_traded = {}% v; @% k4 c+ Y) U5 N
; Z! G6 f% w4 E5 E; i. U; H
# 首版5 _3 k9 R$ m- a- a v: y" k4 f& ^
self.candidatesFirstBan = {} # 进入候选观察股池
# q3 f+ @0 e1 q self.days_since_in_pool_firstBan = {} # 记录每只股票买入后的交易日数' U0 C' n/ F4 ~7 g- b
( v7 V D; e4 \' \6 e2 q' T # 波动小
- Y$ w0 M2 _# L- S) W. y* v5 c self.candidatesLowWave = {} # 进入候选观察股池
9 Q* R* ` \( @6 v: e. X self.days_since_in_pool_lowWave = {} # 记录每只股票买入后的交易日数/ ?; `) [" ?; R7 @$ @1 ~& V X
4 Z0 S9 ]0 w6 c ?" y
# 构建文件名
/ M1 [7 L* g. j filename = f"首板打炸板持有{self.p.hostDays}天早盘非涨停卖出策略_tdx2023_全部.csv"
; g. H/ c5 Q- Q) s, B1 l, q self.logFile = "../out/"+ filename# Y7 h6 I! g8 a& ^+ b
, [/ }, b0 }# I1 h with open(self.logFile, 'w', newline='', encoding='utf-8') as file:" i$ q. v @$ y4 R( Z& \5 H5 p% J
writer = csv.writer(file)
; R* B# l3 L3 w$ h( E7 U writer.writerow(['Type', 'Code', 'PriceType', 'Price', 'Date'])
$ B+ K1 A, I! R) n0 Z( e$ }- R+ {4 k
self.profit_trades = 0 # 交易盈利次数# K& d9 G; [1 x
self.profit_3percent = 0 # 交易盈利3%比例
h+ n6 c) l% i1 p+ M3 I( _ self.stop_loss_trades = 0 # 割肉次数+ ?9 x$ t' G5 b2 X, A+ W9 s" T# ?
self.profit_pct = 0.0 # 盈利时的平均股价涨幅, f. Z# t: \6 [# b, q. T
self.stop_loss_pct = 0.0 # 割肉时的平均股价跌幅
% K' c) d1 R0 O7 S4 b6 f self.profit_trades_open = 0 # 交易盈利次数
$ `, g# h- {: i. l6 R% X self.stop_loss_trades_open = 0 # 割肉次数
: a: {0 T# F5 H: m' @4 v self.profit_pct_open = 0.0 # 盈利时的平均股价涨幅4 s2 Y( Z0 e, J" f$ n
self.stop_loss_pct_open = 0.0 # 割肉时的平均股价跌幅. ]1 j5 O2 g6 {; u7 ]6 E$ I
self.count = 0.0 # 统计资金不足次数
% ]2 [9 X: n8 s6 ]: ?, ?$ s" C8 V8 G9 b% \6 s
to_remove = [] # 用于存储需要移除的数据对象7 j* E* @2 H$ Y
for i, d in enumerate(self.datas):
8 G, k, J& h+ p; a% E if len(d.array) < self.params.LOW_WAVE_PERIOD: # 检查数据长度是否足够
7 @: `) D. N' s5 G! M8 ^ # print(d._name + '=' + str(len(d.array)))* g0 y( S8 \1 @. ~7 i( Y# z
to_remove.append(i) # 将需要移除的数据对象添加到临时列表
5 w# W: L( |/ r" A else:
6 W! X" |7 {( l) | self.inds[d] = dict()+ j- W+ ?9 |8 l; a; a
self.inds[d]['lowesstWave'] = bt.ind.Lowest(d.low, period=self.params.LOW_WAVE_PERIOD)0 `1 G+ i2 N9 Z* F0 L+ h& l
self.inds[d]['highestWave'] = bt.ind.Highest(d.high, period=self.params.LOW_WAVE_PERIOD)4 E- z# T. c$ W2 F& x' ~; ^/ p
9 z2 R( N: W3 R1 f; V
# 移除数据对象
& ?9 Z$ b) f% Z+ M/ k for i in reversed(to_remove):
8 G* ~0 ?8 E& Y$ w0 b( k1 |, P. l self.datas.pop(i) # 使用pop()方法移除指定索引处的数据对象) ]4 M8 l, M! [5 D: _. P
. Z1 W+ i7 p9 N% m
# 检查剩余的数据对象数量8 G) T9 g8 [5 M! k. T7 P* ^+ p. B
if len(self.datas) == 0:" }$ X: m$ C0 f1 z$ S/ `# {
print("所有数据对象已移除,无法运行策略")
, q- g% U4 Q$ @7 ~1 g9 \7 L self.stop() # 中止策略的执行
; ~9 b3 O9 A0 z. \7 D. A# K$ D( @) }1 F
def prenext(self):
3 P9 r( ~' F8 }( w) {* X9 p% c self.next()
7 c/ T5 b* U2 I1 S* J' _! f
) X" x$ ~( }. X8 l- d+ l def notify_trade(self, trade):
9 p8 ~4 W% U! |0 ~ if trade.isclosed:' u3 S, T+ y1 D3 ]2 ?9 ^, o! Y& d
if trade.pnlcomm > 0:
! D* J; \% f! Q5 i0 R: m2 s5 I3 Q self.profit_trades_open += 1; o6 M! _% f6 D$ j3 Q7 E
self.profit_pct_open += trade.pnlcomm. z$ k* h2 n6 d$ I. _" D
else:) L, D* ~! b8 O9 P- m* ?3 F
self.stop_loss_trades_open += 13 I$ v% @; }7 O; S
self.stop_loss_pct_open += trade.pnlcomm ]" V# b: G/ h! Q
0 z; _* c& ~% f9 k+ I1 A if self.selfTrader.isclose(symbol= trade.data._name):
6 t* W$ [" n2 y! O! H1 K if self.selfTrader.positions[trade.data._name]['pnl'] > 0:
2 u) w( J+ [5 O7 @/ d self.profit_trades += 1
2 j/ U0 ?. r c7 ~) \- ^7 c self.profit_pct += self.selfTrader.positions[trade.data._name]['pnl']
1 R: E* r- g- R9 A O- I self.log("盈利", trade.data._name, "收益", self.selfTrader.positions[trade.data._name]['pnl'], trade.data.datetime.date(-1))
: i3 r$ d8 B6 ? \/ T if self.selfTrader.positions[trade.data._name]['pnl'] >= 300:
7 n! h3 P, T& D1 M self.profit_3percent += 1
, u1 ~5 a- s6 R! ~8 J& i else:
$ [8 u9 {/ X' d0 ^9 k if self.selfTrader.positions[trade.data._name]['pnl'] > -2500:
+ |) [1 J4 i$ l self.stop_loss_trades += 10 q; m8 A0 _. l
self.stop_loss_pct += self.selfTrader.positions[trade.data._name]['pnl']
, c3 ~1 W4 z" x; T/ a/ F- ^ self.log("割肉", trade.data._name, "损失", self.selfTrader.positions[trade.data._name]['pnl'], trade.data.datetime.date(-1))
$ [5 P2 Q7 Y6 [) a# N. m$ | else:9 p" q( K. u" J2 v9 P' o8 u8 R
self.log("可能有除权", trade.data._name, "问题数据", self.selfTrader.positions[trade.data._name]['pnl'], trade.data.datetime.date(-1))9 I# T6 s& |3 ]5 l1 G
+ q& \3 A: d4 z3 [0 N( ]
) ~4 a6 V& H l def stop(self):6 z1 \* y, }; v% N
if self.profit_trades > 0:0 d4 \5 T8 J% B% ~, p4 l
self.profit_pct /= self.profit_trades; Q" \9 Q/ t) @8 [0 d+ o" O8 h. s% q! Y
if self.stop_loss_trades > 0: V8 ?. B, r5 z2 j; ~
self.stop_loss_pct /= self.stop_loss_trades
, |9 J8 U: x- n* S: P% T* J6 {$ M- z6 g' w9 `- N
if self.profit_trades_open > 0:
% @# B y7 V8 Y/ U7 Q: b% b8 n# P self.profit_pct_open /= self.profit_trades_open3 N; i, f9 h/ O2 f( x: v: ~ v% {
if self.stop_loss_trades_open > 0:8 A [2 S" P2 c' V3 [. t
self.stop_loss_pct_open /= self.stop_loss_trades_open* h$ c1 z% a* a" o& z& x3 B
# 打印交易统计信息1 S. ^. U8 N8 p8 S2 l( e
print("交易盈利次数:", self.profit_trades)
* B+ f0 m2 n6 e/ R. N. F( H7 x print("割肉次数:", self.stop_loss_trades)1 |6 |7 k2 `3 R( U0 Q3 r
print("资金不足次数:", self.count)+ ?$ ~! N, H/ @ u
print("盈利时的平均盈利金额: %.2f%%" %self.profit_pct)
; _, C& |) Z2 r print("割肉时的平均亏损金额: %.2f%%" %self.stop_loss_pct)2 k. }2 ?* T' X% ~: k
print('Final Portfolio Value by Close trade: %.2f' % self.selfTrader.get_total_assets())
) }9 o9 d+ w) v2 O- b9 m; c- R if self.profit_trades + self.stop_loss_trades > 0:
9 {8 g5 |& l% I/ f' ~) C percent = self.profit_3percent / (self.profit_trades + self.stop_loss_trades) * 1006 e. r3 L, O+ }8 e) j f
print(f'盈利超过3%的比例: {percent:.2f}%')
+ K: v5 m* t4 C. O2 s7 W6 i7 X2 ` self.log(self.profit_trades, self.stop_loss_trades, self.profit_pct, self.stop_loss_pct, self.selfTrader.get_total_assets())6 ?( b9 V0 t5 X* M7 t3 f0 H
) g% X# W) I) _+ o% ~ current_date = datetime.datetime.now().strftime("%Y-%m-%d")
. i: |- {3 j" b: K0 f$ o: @2 J outFile = f'../out/candidates{current_date}.txt'
* U8 |/ b2 Q; ` L2 r( ~9 w- U, Y # 检查文件output.csv是否存在
4 A( \# C+ `4 C9 D" G if not os.path.exists(outFile):
% }' ^8 F- N( H4 J with open(outFile, mode='w', newline='', encoding='utf-8') as file:
! g C; Z4 @1 X& o0 d writer = csv.writer(file)% d8 L2 L1 E5 k! [; U$ I$ c) u; y9 c
writer.writerow(['代码', 'TargetPrice', 'Type', 'LastClose'])6 w, g& h, L' V! S0 a1 c1 `
, R) w. B e5 Y1 r$ ~0 ~! b with open(outFile, mode='a', newline='', encoding='utf-8') as file:6 \4 I# q& G1 a6 T9 `
writer = csv.writer(file), v2 o' o2 m9 D, j2 N% y# a
for stock in self.candidatesFirstBan:
' H2 F. n9 J& P5 t if stock not in self.positionsMy:
1 M' S/ F& o& J% L( U$ h7 ? writer.writerow([stock._name[:-4], round(self.candidatesFirstBan[stock][1] * 1.1 ,2), '炸板',self.candidatesFirstBan[stock][1]])
: Y# G, ?% C j/ c; }
' N$ \0 U ^5 l, L! D allStockList = r'../in/all_stocks.csv', f; e/ a4 Q0 G" I2 ]1 A c3 N
df_all = pd.read_csv(allStockList, header=0, na_values='NA')4 g/ M3 t- U2 v% G$ e; N
df = pd.read_csv(outFile, header=0, na_values='NA')6 Q2 ~1 ? m0 [4 {" b0 W
merged_df = pd.merge(df, df_all, on='代码', how='left')
2 |: [ w( E# ~0 q4 u5 X out_df = merged_df[['代码', 'TargetPrice', 'Type', 'LastClose', '名称', '市盈率', '概念']]2 N/ W6 u+ d; K
out_df.drop_duplicates(subset=['代码'], keep='first', inplace=True)8 I1 \* }/ h& w$ H8 n3 f9 V
out_df.to_csv('../out/ZhaBancandidates.csv', mode='w', header=True, index=False)
! A% P" B# g, {
& s2 [/ `2 Y0 H def highIntervl(self, data, start_time, end_time):3 D) a7 x# U7 i. K- y
stock_name = data._name[2:] # 获取股票名
/ M/ |" t; P$ m& E; Y# R stock_data_path = os.path.join(self.p.Min_FOLDER, stock_name)' X: _; Q9 X: T7 s3 B
if os.path.isfile(stock_data_path):3 ]9 T3 l1 `% y# N/ Q$ v7 o1 {
df = pd.read_csv(stock_data_path, parse_dates=['date'])
/ W1 r6 o! m' @6 n9 R! t3 x2 y # 设置筛选条件
$ p) K# r3 S0 k2 G& H% ~6 z next_date = data.datetime.date(0)
5 H3 A2 v- N: x; f1 t next_date_data = df[df['date'].dt.date == next_date]4 D( q: Z1 z8 z+ T, A" u5 b
while next_date_data.empty:2 n/ s7 z- B5 q2 \( C7 ]
next_date += datetime.timedelta(days=1)
3 j q' U! s$ U: H if next_date >= datetime.datetime.now().date():
1 q4 `1 d9 x3 x! l1 m) | self.log("异常:可能停牌", data._name, "价格", "", data.datetime.date(0))" b- j& A4 n2 K- P K/ F
break
! W H" i- ]" Z- C/ P! B next_date_data = df[df['date'].dt.date == next_date]
; i7 M0 b' R1 o: X7 u$ P: c3 _ condition = (next_date_data['date'].dt.time > pd.to_datetime(start_time).time()) & (next_date_data['date'].dt.time <= pd.to_datetime(end_time).time())
& r1 y+ ^ t& x; b8 K5 @/ Y # 根据条件筛选数据
0 X3 O( s- t: } l6 f filtered_df = next_date_data[condition] L; f# h0 b& J1 P0 z7 |7 H/ g+ B3 o
) |. }# L z$ W* ^( v # 获取筛选后数据中 'high' 列的最大值+ T( M; i/ V6 k$ i" X9 u
max_high = filtered_df['high'].max()7 ` w9 r+ e2 B- A& m
low_min = filtered_df['low'].min()
' y& L- `& _& `8 z7 [+ a. i& I" p# {" p1 P+ q/ I3 D. V0 E
return (max_high, low_min)# G" F1 o; V3 K. @; l4 F
else:
8 n' h4 d& S8 Z8 b raise f"{stock_data_path} is not exit"6 X }. e8 T0 X' Q# v, W
( l1 e7 T8 u' ~8 a
def LowWaveSelect(self, data):5 U& x/ c2 k# Q6 R
if not self.upLimit(self.inds[data]['highestWave'][0], self.inds[data]['lowesstWave'][0], self.p.LOW_WAVE_RATE):+ z. @# F: q' a& Q4 l
self.candidatesLowWave[data] = data.close[0] #: [5 A& S) ?. s' a" I0 X" e
# self.log("添加lowest后选股池", data._name, "价格", data.close[0], data.datetime.date(0))& p. B3 Z9 j- H; }' S$ k6 ?+ U
6 u7 b/ R1 U- u. E5 v: y
# 判断是否需要移除候选观察股池中的股票,在股票池超过stock_day个交易日
" Z0 d0 C7 G8 d; ]5 o4 U if data in self.candidatesLowWave:$ W. R- m' A5 p' l& r* n
if self.upLimit(self.inds[data]['highestWave'][0], self.inds[data]['lowesstWave'][0], self.p.LOW_WAVE_RATE):
) k6 T, y& J8 Z self.candidatesLowWave.pop(data, None)" F4 K, k! E8 {; g" q1 k7 h3 w
4 N5 N+ ^* D% d
# self.log("移除Lowest后选股池", data._name, "价格", data.close[0], data.datetime.date(0))- y' |. ~' Y7 G7 G$ Y U3 x
, X1 x; I2 t9 I8 D6 H0 ~. Z' B$ Y def FisrtBanSelect(self, data, prev_close):7 v7 L% G. B5 O* E" j
if data in self.candidatesFirstBan:
. I7 z+ M* w5 N8 l8 n self.days_since_in_pool_firstBan[data] += 15 _) b i* `+ r2 [* `, ]8 S
& L% I7 ^& P% W9 M7 U current_date = self.data.datetime.date()
% y9 w+ K4 j3 ^4 f9 c if self.upLimit(data.high[0], prev_close, self.p.UP_LIMIT) and not self.upLimit(data.high[-1], data.close[-2], self.p.UP_LIMIT): # 乘以1.0998相当于增长9.98%. v4 c; O0 {. |. [: e( U! U) I
stock_name = data._name[2:] # 获取股票名
% k' |/ R1 Y2 g c4 M$ w2 T if stock_name not in self.candidatesFirstBan:
. |: U2 y' \( [9 y/ ]6 I. k self.candidatesFirstBan[data] = (current_date, prev_close)* x% ]0 D! C8 K8 ?3 q
self.days_since_in_pool_firstBan[data] = 0. d5 V1 j- a7 S$ V5 i& o5 C: G
# self.log("添加首版选股池", data._name, "价格", data.close[0], data.datetime.date(0))* n2 u, V8 A' F" V: {7 F# g7 {, ? s
+ }- _. {/ u/ h1 ~" @) L E5 \
if data in self.candidatesFirstBan:9 R$ \/ l, t& D. V
if self.days_since_in_pool_firstBan[data] > 0:% l7 J: {: Z1 m: A+ ?% z5 T
self.candidatesFirstBan.pop(data, None), ? ]8 e: l7 C8 U, S: m% O+ M
self.days_since_in_pool_firstBan.pop(data, None)7 }& A4 ^" I f. b
# self.log("移除首版选股池", data._name, "价格", data.close[0], data.datetime.date(0))
% b; Q% S1 @4 i, y, @2 ^
% |& K% H- o( h( n' o: {/ `7 v' |0 A5 }; _" q8 M4 D8 K! U
def upLimit(self, currentPrice, lastClose, upLimit):; u' b7 @( ~3 M+ N1 }- O6 `1 d! }% X
if currentPrice >= round(upLimit * lastClose + lastClose,2) :
" C E' }2 A1 K/ f, p" \/ I; G return True
, x& T+ n) Q5 x else:, B7 L4 G | \8 X* N
return False
* c( L$ l) y- t1 C
2 E- t0 m) ~8 J" S def downLimit(self, currentPrice, lastClose, downLimit):
1 A8 e6 k% m( s5 a! }9 c$ A if currentPrice - lastClose < downLimit * lastClose:- x4 ^+ |' T# P" J+ @5 f/ ]+ p
return True
9 P8 {3 s# r: a- f: \. R else:1 b+ `9 A9 ^1 J4 g2 b; y
return False6 e! E7 v8 D: B5 B- Y
t4 i. j0 c! _0 u& ^( A2 O) q3 p: q
def next(self):
( x5 y2 u$ N- P5 { for data in self.datas:8 J, I3 ?% p) B7 ?
if data not in self.first_day_traded:
, B, ]4 p& @' t self.first_day_traded[data] = True/ K( l3 h% Q& y3 r
self.diff_start_dates_days[data] = 01 E: b( n* Y* p1 q( D
continue # 跳过第一天交易判断
V% [; b3 t$ m# q* r& B; ^ ]" Q
7 y0 m. P5 M7 O# Y8 {; z7 s if data not in self.start_dates:8 z- @$ H8 A# u5 k. I' L) m- Q2 T
self.start_dates[data] = data.datetime.date(0)" |+ v/ E& R. m+ A
self.start_dates_flag[data] = False5 c) j! Y- x: ?4 t4 V& x
continue
- s% a7 @1 T( L: z+ l if (data.datetime.date(0) - self.start_dates[data]).days != 0 and not self.start_dates_flag[data]:5 a6 W4 Z0 O" V" u1 `
self.start_dates_flag[data] = True! ~* Y' b5 H7 k9 a) T4 [8 e4 A2 n. a# U
self.start_dates[data] = data.datetime.date(0)
# A# ^9 I0 g v; D j( }& L. N continue F3 g% q k |' W
# 当股票当前日大于起始日30天以上才进行计算,保证新股上市30天后才计算,
( G( O' S. ?( |+ f' Z( S/ S if self.start_dates_flag[data] and (data.datetime.date(0) - self.start_dates[data]).days > 30 \
) W, y* a j- `; o% z, c9 ]$ [ and (data.datetime.date(0) - self.start_dates[data]).days > self.diff_start_dates_days[data]:& G9 @& ]+ r' `' S' {$ _ }
self.diff_start_dates_days[data] = (data.datetime.date(0) - self.start_dates[data]).days3 Q! a& L1 V9 |! @. w3 b
#设置前一日价格
! |6 w# i/ H$ R dividend_flag = False
+ a! q- Q( G0 S" u- F prev_close = data.close[-1]% M9 I7 L* J' m4 Z
if data.open[0] - prev_close < -0.11 * prev_close and data._name.startswith(('sh60', 'sz00')) or \& s) \5 U6 j& `2 Q* F, h
data.open[0] - prev_close < -0.21 * prev_close and data._name.startswith(('sh68', 'sz30')):2 O1 r e4 k, P7 z5 t
dividend_adjustment = prev_close / data.open[0]6 @# Y/ x) \5 O2 H7 _8 ]. _$ n
prev_close /= dividend_adjustment
3 i) K. Z2 T/ g* l dividend_flag = True
/ z. P% @, M( u2 T$ F- c$ e" L, c, ^ Z$ ~" T! Z
self.FisrtBanSelect(data, prev_close)
. q8 A( b9 {% a D. y self.LowWaveSelect(data)5 Q: M- n L2 J' i; d
7 D! B: O$ k# T3 c* F # 判断是否需要卖出持仓股票6 Q- @7 C& D& C6 _% u9 n; E
if data in self.positionsMy:$ w6 S1 x; c" x5 t( b
self.days_since_in_host[data] += 1' R: q3 X4 O; G% [& R3 p! c
#除权发生调整成本
9 a8 |& v& I: l1 B( s" f if dividend_flag:
3 b6 {3 U8 k! U: `) a, ]$ j self.selfTrader.positions[data._name]['price'] /= dividend_adjustment
, O7 P r- [: {3 Y, F# d* Q( L self.selfTrader.positions[data._name]['quantity'] *= dividend_adjustment, Z# z; T$ t) p% L i: h: z
self.log("除权发生", data._name, "调整成本价", self.selfTrader.positions[data._name]['price'], data.datetime.date(0)) e/ v/ W8 f6 B2 F/ M! A
" D. `6 g+ @" s5 L stock_name = data._name[2:] # 获取股票名
/ J) V- W0 y! H( f2 c0 {8 ]: b stock_data_path = os.path.join(self.p.Min_FOLDER, stock_name)
0 @1 f* q L9 \' {! @# x0 s( }0 O if os.path.isfile(stock_data_path):
) X; d9 c D- v% [( }, p df = pd.read_csv(stock_data_path, parse_dates=['date'], index_col='date')
$ K4 O j/ J- Q. [5 R next_date = data.datetime.date(0)1 G( j @! k6 K: ]# j
next_date_data = df[df.index.date == next_date]/ h7 w$ q) l$ ]1 j; }* G' S% G7 f
while next_date_data.empty:' ]" d3 A: p# V2 y5 l' v
next_date += datetime.timedelta(days=1)
! l" E) K5 ^- d! g) V: q/ } if next_date >= datetime.datetime.now().date():
& W' b# ^. c/ K8 S3 i5 U) c9 f self.log("异常:可能停牌", data._name, "价格", "", data.datetime.date(0))
/ Q" A" ^. C# L* P3 d# V7 W4 D break5 a# a7 w6 N5 C8 W" P
next_date_data = df[df.index.date == next_date]' }/ s- J1 l+ d1 R) u X
6 T8 J* n, _& h$ p# J4 q
highInterval = self.highIntervl(data, self.p.START_TIME, self.p.END_TIME)' m W7 [- R! _' X
close_price = (highInterval[0] - highInterval[1]) * 0.8 + highInterval[1]
: q/ E; X' r* U; B9 J1 a) D4 v' Q
5 H9 k% x8 V6 E) H if not self.upLimit(close_price, prev_close, self.p.UP_LIMIT):9 W' F! L# b( J' h- V) T1 {
sell_price = close_price
W7 O5 E9 s8 s9 r self.log("早盘卖", data._name, "价格", sell_price, next_date.strftime('%Y-%m-%d'))9 ~ c7 F+ e* f: V! D, f% k z
self.close(data=data, price=sell_price)$ }5 i" B: R5 Q% O9 j& p- ? L- h
self.selfTrader.close(symbol=data._name, price=sell_price)
1 ^: s9 t B0 ^3 i6 g5 _+ k- C5 v% Y self.positionsMy.pop(data, None)% ^4 t, z+ M9 j ~( v
# 必须要在卖出股票后移除candidates1股池,否则,卖出当天有可能还会进入买入逻辑,从而导致当天无法完成结算,而且反包规则卖出当天都不符合买入准则7 M( a0 B; x. P2 S8 I+ L7 i5 d
self.candidatesFirstBan.pop(data, None)
% v* M# r0 U2 E1 L else:
. Z3 p; T; Q) Y; j) u$ C0 i Z self.log("涨停不卖", data._name, "涨停价格", data.close[0], data.datetime.date(0))
" I6 u2 ^' h/ t# L8 r
+ [0 ?5 Z, t: l3 F" o. o/ x
0 z! H: n5 ]1 J # 判断是否在候选观察股池中,买入炸板股票根据规则# `1 q3 |5 _% _1 P! @! Q L8 B. k
if data in self.candidatesFirstBan and data in self.candidatesLowWave:
# h( @; @4 G1 R( D stock_name = data._name[2:] # 获取股票名0 Y2 v! B/ [$ e( r
stock_data_path = os.path.join(self.p.Min_FOLDER, stock_name)
* `" x0 y' _1 A7 v8 n# l7 A if os.path.isfile(stock_data_path):# a9 r6 x+ X5 Y: w( b6 i
df = pd.read_csv(stock_data_path, parse_dates=['date'], index_col='date')
3 P9 W0 G0 h/ O7 u# J) e df = df[df.index.date == self.candidatesFirstBan[data][0]] # 选择日期部分等于current_date的数据
2 c1 z! w A' T& t4 {" B firstUpFlag = True _$ c; F' R$ p; l/ j8 u
secondUpFlag = True
+ P% Y$ n4 {8 f# X- | for index, row in df.iterrows():
. K# Y2 ^7 r9 x( f, G high = row['high']" @* ?$ D: k' ]: N& _+ m
low = row['low'], Y5 @* v- ~" k* k) c) y
datevalue = index.time()
u: _5 {' q4 ^& P+ q4 f( D
8 |8 e K7 H. g" b5 k/ i! R* h if self.upLimit(high, prev_close, self.p.UP_LIMIT) and firstUpFlag and secondUpFlag:
, A7 V$ D |$ p9 d, |" ?8 L # 首次涨停
: l9 w; ^8 v1 E0 d firstUpFlag = False6 v8 y1 S B8 g2 M m$ @! f
continue, H* Y6 U, E3 j5 z, |- H
if self.upLimit(high, prev_close, self.p.UP_LIMIT) and not firstUpFlag and secondUpFlag and low < high:" I/ c- q! e! s* R
# 打开涨停版/ y/ _3 j% j) c6 q# V7 F5 u
secondUpFlag = False
# }* |! L' o: k! h continue
& e) W" R+ `/ V2 ?7 v if not firstUpFlag and self.upLimit(high, prev_close, self.p.UP_LIMIT) and low < high and not secondUpFlag : w; v3 g( J3 E) A* e0 y7 ^
# 再次准备涨停
; W a- G" {: X& o! y cash = self.broker.getcash()
7 G4 a; v, S* B: B if cash < 10000:
$ d2 n. s5 h1 g! r$ | self.log("资金不足", data._name, "持有股数量", len(self.positionsMy),
3 ]* ~$ C l6 s% d data.datetime.date(0))- B2 G. m2 m0 x' Z1 P
self.count = self.count + 1# c) E/ B2 O. H" A
elif data not in self.positionsMy:9 I4 S0 s* h N- q
buyPrice = round(prev_close * 1.1 ,2)
# o- a2 |+ l- I4 A- u0 v sizeOpen = 10000 / buyPrice# @: v- p# |: E$ `& m
self.buy(data=data, size=sizeOpen)
. [5 h/ D# Y/ a$ C7 _ self.positionsMy[data] = buyPrice
( K* L5 Q! q8 c" Q, C V) R! e" _ self.log("买入股票", data._name, "买入价格", buyPrice, index.strftime('%Y-%m-%d %H:%M:%S'))
4 i( q% t. W& t7 T4 Z+ y" u self.days_since_in_host[data] = 1) k2 I* ]% W; F6 M G$ ^$ F3 i
sizeClose = 10000 / buyPrice
8 M( p' H( ~) V$ u self.selfTrader.buy(symbol=data._name, price=buyPrice, quantity=sizeClose)
7 a+ \3 X/ E' M( t6 j: A; ? break
6 Q. [% R1 ?. g+ ^: s" e- x1 |3 [; F+ P$ z# K6 B
5 p5 a5 `) Z; f" m. R9 i6 [, x$ d' Pnow = datetime.datetime.now()2 }# V1 T U% t; v9 `" a
one_day = datetime.timedelta(days=1)
$ L! b) x L- O* fnext_day = now + one_day2 f* q) K; e* _, |6 O
class MyData(bt.feeds.GenericCSVData):$ A4 F! t& ?6 _( p8 g& W
params = (8 i/ ?8 y/ g% b0 r o# r
('dtformat', '%Y-%m-%d'),
3 g$ c9 e5 B8 \! f$ Q: }8 @ ('datetime', 0),: {# P; E; k4 Y" E- z9 U; ~2 {
('open', 1),
5 z8 {1 C) ~$ m# m9 a& U ('high', 2),
/ @8 w/ u5 c% U0 N, m ('low', 3),) m+ q3 z0 [! Q3 s+ t( }; Y
('close', 4),
5 a/ y, [ [) ~( k' m ('volume', 6),
' h0 b$ Y1 L; I" _0 _( x ('openinterest', -1),
, F* N' R9 S7 Y+ O; Y- v3 R ('fromdate', datetime.datetime(2022, 12, 1)),, ?1 h0 [! e& k0 U2 W
('todate', next_day),
& X: `# j0 t3 @' R' _) ^( R) K )
7 K' V8 k' u+ `; r% @& Z
: ^0 T" j: l" i4 [if __name__ == '__main__':
$ V0 ?, R. X/ e time_start = time.time()
6 @5 W) n$ Q& e% } cerebro = bt.Cerebro(stdstats=False, cheat_on_open=False)( |8 e( _8 V8 L. E4 L% ?; [& S
: G4 k# I& X0 l# p$ T- Q
# 添加数据源 v1 \: b) Q. l" {& p6 C
dataFolder = r'/Users/Downloads/tdx/baostock'
+ q5 e3 @- b" l: H& G! R- r allStockList = r'../in/all_stocks.csv'
2 J5 u+ [# ]( @ df_all = pd.read_csv(allStockList, header=0, na_values='NA')
; u0 ?; _# C6 a* G8 q, d
* B/ F% X) |7 J2 Q' k+ C stock_lists = os.listdir(dataFolder)
: R3 F" o6 k5 m' H* ]% p" j
% h. { h. }6 i; @; ]# stock_lists = ['sh600132.csv']
2 w) @8 y) k8 g' z for stock in stock_lists:
8 N- F9 {- [6 i- _ name = stock.replace(".csv", "")
9 ?4 P9 N2 l- q8 g4 h8 u. d$ p new_df = df_all[df_all['代码'] == name]
% ~- |: [3 A2 c3 Q v if len(new_df) > 0:* \3 e% c& B& a% r! }7 @
first_row = new_df.iloc[0]
7 b, d- j: }2 Q; Z- W" Y if 'S' not in first_row['名称'] and '*' not in first_row['名称'] and '退' not in first_row['名称'] and 'X' not in first_row['名称']\
( e$ t( C8 y( q# W% _ and 'D' not in first_row['名称'] and 'C' not in first_row['名称'] and 'U' not in first_row['名称'] :
2 c8 V0 q4 W5 g% d' V # and first_row['市盈率'] > 0 and first_row['市盈率'] < 100:4 [) o) Z, q1 J# @' e
filepath = os.path.join(dataFolder, f'{stock}')
4 i* W; V' F% l! i9 [$ q7 E size = os.path.getsize(filepath) z; A( T) q2 j( i) {& h2 D) F
if size > 10 * 200:
8 }% O% ?/ l( r) u+ T3 e# ~& M if stock.startswith(('sh60', 'sz00')):/ y l/ `+ @) z
df = pd.read_csv(filepath)# w. c( h3 v, m) W5 v; n5 g
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
6 t4 k7 S8 i: A+ R last_row = df.tail(1)
' O, B6 j# v& t% v9 r if last_row['date'].values[0] > np.datetime64('2023-05-18'): O, O3 F+ N2 I0 o4 o
cerebro.adddata(MyData(dataname=filepath), name = stock)
# T) I" |, J$ k; Z( R9 a& Y' F9 D( F( A& f& F- r( j
# 添加交易记录* z* u2 a0 S% Z) Q; z" t1 {
cerebro.broker.setcash(10000000.0)
7 v* f0 l: E6 B& t+ T5 {( C cerebro.broker.setcommission(commission=0.0007)
' }( \! x4 h5 h5 w$ p. h' W. d* b) A" \5 Q3 |) R! m4 k
cerebro.addstrategy(ZhaBanSelect)
- L! `; m; |. h, B8 M& O1 z \. P
' ^+ @. U( i. ?! |' Z9 d4 M cerebro.run()
3 O; |0 ]( d$ R5 z5 Z time_end = time.time()
0 W' y$ \- ?6 t+ W" p' t1 U4 r4 K$ }2 Y: h; }3 x i6 z
print('程序所耗时间:', time_end - time_start) |