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