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