模拟北京抄家胜率60%的打炸板首版股票回测代码分享

[复制链接]
查看403 | 回复0 | 2023-10-1 10:24:30 | 显示全部楼层 |阅读模式
北炒是游资中打板一族的一个另类,他总是以打炸板股出名,今天给大家把回测代码也分享了,可以自己去调试优化,如果能卖在早盘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
模拟北京抄家胜率60%的打炸板首版股票回测代码分享-1.jpg
; E4 W8 X3 c; J9 k$ R这是他在23-8-18的交易记录:
2 z9 y! I" t) s: A; E/ w# H4 |7 N8 B. V
模拟北京抄家胜率60%的打炸板首版股票回测代码分享-2.jpg
& 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 + &#39;=&#39; + 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][&#39;lowesstWave&#39;] = bt.ind.Lowest(d.low, period=self.params.LOW_WAVE_PERIOD)0 `1 G+ i2 N9 Z* F0 L+ h& l
                self.inds[d][&#39;highestWave&#39;] = 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(&#34;所有数据对象已移除,无法运行策略&#34;)
, 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][&#39;pnl&#39;] > 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][&#39;pnl&#39;]
1 R: E* r- g- R9 A  O- I                self.log(&#34;盈利&#34;, trade.data._name, &#34;收益&#34;, self.selfTrader.positions[trade.data._name][&#39;pnl&#39;], trade.data.datetime.date(-1))
: i3 r$ d8 B6 ?  \/ T                if self.selfTrader.positions[trade.data._name][&#39;pnl&#39;] >= 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][&#39;pnl&#39;] > -2500:
+ |) [1 J4 i$ l                    self.stop_loss_trades += 10 q; m8 A0 _. l
                    self.stop_loss_pct += self.selfTrader.positions[trade.data._name][&#39;pnl&#39;]
, c3 ~1 W4 z" x; T/ a/ F- ^                    self.log(&#34;割肉&#34;, trade.data._name, &#34;损失&#34;, self.selfTrader.positions[trade.data._name][&#39;pnl&#39;], 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(&#34;可能有除权&#34;, trade.data._name, &#34;问题数据&#34;, self.selfTrader.positions[trade.data._name][&#39;pnl&#39;], 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(&#34;交易盈利次数:&#34;, self.profit_trades)
* B+ f0 m2 n6 e/ R. N. F( H7 x        print(&#34;割肉次数:&#34;, self.stop_loss_trades)1 |6 |7 k2 `3 R( U0 Q3 r
        print(&#34;资金不足次数:&#34;, self.count)+ ?$ ~! N, H/ @  u
        print(&#34;盈利时的平均盈利金额: %.2f%%&#34; %self.profit_pct)
; _, C& |) Z2 r        print(&#34;割肉时的平均亏损金额: %.2f%%&#34; %self.stop_loss_pct)2 k. }2 ?* T' X% ~: k
        print(&#39;Final Portfolio Value by Close trade: %.2f&#39; % 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&#39;盈利超过3%的比例:   {percent:.2f}%&#39;)
+ 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(&#34;%Y-%m-%d&#34;)
. i: |- {3 j" b: K0 f$ o: @2 J        outFile = f&#39;../out/candidates{current_date}.txt&#39;
* 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=&#39;w&#39;, newline=&#39;&#39;, encoding=&#39;utf-8&#39;) 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([&#39;代码&#39;, &#39;TargetPrice&#39;, &#39;Type&#39;, &#39;LastClose&#39;])6 w, g& h, L' V! S0 a1 c1 `

, R) w. B  e5 Y1 r$ ~0 ~! b        with open(outFile, mode=&#39;a&#39;, newline=&#39;&#39;, encoding=&#39;utf-8&#39;) 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), &#39;炸板&#39;,self.candidatesFirstBan[stock][1]])
: Y# G, ?% C  j/ c; }
' N$ \0 U  ^5 l, L! D        allStockList = r&#39;../in/all_stocks.csv&#39;, f; e/ a4 Q0 G" I2 ]1 A  c3 N
        df_all = pd.read_csv(allStockList, header=0, na_values=&#39;NA&#39;)4 g/ M3 t- U2 v% G$ e; N
        df = pd.read_csv(outFile, header=0, na_values=&#39;NA&#39;)6 Q2 ~1 ?  m0 [4 {" b0 W
        merged_df = pd.merge(df, df_all, on=&#39;代码&#39;, how=&#39;left&#39;)
2 |: [  w( E# ~0 q4 u5 X        out_df = merged_df[[&#39;代码&#39;, &#39;TargetPrice&#39;, &#39;Type&#39;, &#39;LastClose&#39;, &#39;名称&#39;, &#39;市盈率&#39;, &#39;概念&#39;]]2 N/ W6 u+ d; K
        out_df.drop_duplicates(subset=[&#39;代码&#39;], keep=&#39;first&#39;, inplace=True)8 I1 \* }/ h& w$ H8 n3 f9 V
        out_df.to_csv(&#39;../out/ZhaBancandidates.csv&#39;, mode=&#39;w&#39;, 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=[&#39;date&#39;])
/ 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[&#39;date&#39;].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(&#34;异常:可能停牌&#34;, data._name, &#34;价格&#34;, &#34;&#34;, 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[&#39;date&#39;].dt.date == next_date]
; i7 M0 b' R1 o: X7 u$ P: c3 _            condition = (next_date_data[&#39;date&#39;].dt.time > pd.to_datetime(start_time).time()) & (next_date_data[&#39;date&#39;].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            # 获取筛选后数据中 &#39;high&#39; 列的最大值+ T( M; i/ V6 k$ i" X9 u
            max_high = filtered_df[&#39;high&#39;].max()7 `  w9 r+ e2 B- A& m
            low_min = filtered_df[&#39;low&#39;].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&#34;{stock_data_path} is not exit&#34;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][&#39;highestWave&#39;][0], self.inds[data][&#39;lowesstWave&#39;][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(&#34;添加lowest后选股池&#34;, data._name, &#34;价格&#34;, 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][&#39;highestWave&#39;][0], self.inds[data][&#39;lowesstWave&#39;][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(&#34;移除Lowest后选股池&#34;, data._name, &#34;价格&#34;, 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(&#34;添加首版选股池&#34;, data._name, &#34;价格&#34;, 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(&#34;移除首版选股池&#34;, data._name, &#34;价格&#34;, 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((&#39;sh60&#39;, &#39;sz00&#39;)) or \& s) \5 U6 j& `2 Q* F, h
                        data.open[0] - prev_close < -0.21 * prev_close and data._name.startswith((&#39;sh68&#39;, &#39;sz30&#39;)):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][&#39;price&#39;] /= dividend_adjustment
, O7 P  r- [: {3 Y, F# d* Q( L                        self.selfTrader.positions[data._name][&#39;quantity&#39;] *= dividend_adjustment, Z# z; T$ t) p% L  i: h: z
                        self.log(&#34;除权发生&#34;, data._name, &#34;调整成本价&#34;, self.selfTrader.positions[data._name][&#39;price&#39;], 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=[&#39;date&#39;], index_col=&#39;date&#39;)
$ 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(&#34;异常:可能停牌&#34;, data._name, &#34;价格&#34;, &#34;&#34;,  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(&#34;早盘卖&#34;, data._name, &#34;价格&#34;, sell_price, next_date.strftime(&#39;%Y-%m-%d&#39;))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(&#34;涨停不卖&#34;, data._name, &#34;涨停价格&#34;, 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=[&#39;date&#39;], index_col=&#39;date&#39;)
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[&#39;high&#39;]" @* ?$ D: k' ]: N& _+ m
                            low = row[&#39;low&#39;], 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(&#34;资金不足&#34;, data._name, &#34;持有股数量&#34;, 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(&#34;买入股票&#34;, data._name, &#34;买入价格&#34;, buyPrice, index.strftime(&#39;%Y-%m-%d %H:%M:%S&#39;))
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
        (&#39;dtformat&#39;, &#39;%Y-%m-%d&#39;),
3 g$ c9 e5 B8 \! f$ Q: }8 @        (&#39;datetime&#39;, 0),: {# P; E; k4 Y" E- z9 U; ~2 {
        (&#39;open&#39;, 1),
5 z8 {1 C) ~$ m# m9 a& U        (&#39;high&#39;, 2),
/ @8 w/ u5 c% U0 N, m        (&#39;low&#39;, 3),) m+ q3 z0 [! Q3 s+ t( }; Y
        (&#39;close&#39;, 4),
5 a/ y, [  [) ~( k' m        (&#39;volume&#39;, 6),
' h0 b$ Y1 L; I" _0 _( x        (&#39;openinterest&#39;, -1),
, F* N' R9 S7 Y+ O; Y- v3 R        (&#39;fromdate&#39;, datetime.datetime(2022, 12, 1)),, ?1 h0 [! e& k0 U2 W
        (&#39;todate&#39;, next_day),
& X: `# j0 t3 @' R' _) ^( R) K    )
7 K' V8 k' u+ `; r% @& Z
: ^0 T" j: l" i4 [if __name__ == &#39;__main__&#39;:
$ 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&#39;/Users/Downloads/tdx/baostock&#39;
+ q5 e3 @- b" l: H& G! R- r    allStockList = r&#39;../in/all_stocks.csv&#39;
2 J5 u+ [# ]( @    df_all = pd.read_csv(allStockList, header=0, na_values=&#39;NA&#39;)
; 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 = [&#39;sh600132.csv&#39;]
2 w) @8 y) k8 g' z    for stock in stock_lists:
8 N- F9 {- [6 i- _        name = stock.replace(&#34;.csv&#34;, &#34;&#34;)
9 ?4 P9 N2 l- q8 g4 h8 u. d$ p        new_df = df_all[df_all[&#39;代码&#39;] == 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 &#39;S&#39; not in first_row[&#39;名称&#39;] and &#39;*&#39; not in first_row[&#39;名称&#39;] and &#39;退&#39; not in first_row[&#39;名称&#39;] and &#39;X&#39; not in first_row[&#39;名称&#39;]\
( e$ t( C8 y( q# W% _                    and &#39;D&#39; not in first_row[&#39;名称&#39;] and &#39;C&#39; not in first_row[&#39;名称&#39;] and &#39;U&#39; not in first_row[&#39;名称&#39;] :
2 c8 V0 q4 W5 g% d' V    #              and first_row[&#39;市盈率&#39;] > 0 and first_row[&#39;市盈率&#39;] < 100:4 [) o) Z, q1 J# @' e
                filepath = os.path.join(dataFolder, f&#39;{stock}&#39;)
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((&#39;sh60&#39;, &#39;sz00&#39;)):/ y  l/ `+ @) z
                        df = pd.read_csv(filepath)# w. c( h3 v, m) W5 v; n5 g
                        df[&#39;date&#39;] = pd.to_datetime(df[&#39;date&#39;], format=&#39;%Y-%m-%d&#39;)
6 t4 k7 S8 i: A+ R                        last_row = df.tail(1)
' O, B6 j# v& t% v9 r                        if last_row[&#39;date&#39;].values[0] > np.datetime64(&#39;2023-05-18&#39;):  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(&#39;程序所耗时间:&#39;, time_end - time_start)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

11

金钱

0

收听

0

听众
性别
保密

新手上路

金钱
11 元