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