好友提出要驗證連續下跌買入止盈止損賣出策略,本文對該策略回測和實現做分析記錄。
買入條件中,連續下跌定義為收盤價連續4日低於前1日的收盤價。賣出條件中,止盈率設置為10%,止損率設置為5%。回測初始資金100000元,單筆操作單位1000股,佣金千分之一,回測時間自2018年1月1日至2020年3月6日。
策略核心代碼位於策略類的next方法中:
<code>def
next
(
self
):if
self
.orefs:
return
if
not
self
.position:
lastcloses = list()for
iin
range(self
.p.p_downdays +1
): lastcloses.append(self
.dataclose[-i])if
lastcloses == sorted(lastcloses): close =self
.dataclose[0
] p1 = close * (1.0
-self
.p.limit) p2 = p1 -self
.p.p_stoploss * close p3 = p1 +self
.p.p_takeprofit * close valid1 = datetime.timedelta(self
.p.limdays) valid2 = valid3 = datetime.timedelta(self
.p.limdays2) os =self
.buy_bracket( price=p1, valid=valid1, stopprice=p2, stopargs=dict(valid=valid2), limitprice=p3, limitargs=dict(valid=valid3),)self
.orefs = [o.reffor
oin
os]/<code>
這裡主要應用了backtrader的bracket order,它其實並不是1個訂單,而是有3個訂單構成:1個買單,1個止損賣單,1個止盈賣單。2個賣單就像是把買單用括號括起來一樣,因此合稱為bracket order(括號訂單)。我們所使用的buy_bracket方法遵循以下規則:
- 為了避免被分開執行,3個訂單要被一起提交
- 2個賣單要作為買單的子訂單
- 在買單執行前,2個賣單處於非激活狀態
- 買單如果被取消,2個賣單也會隨之被取消
- 買單被執行後,2個賣單均被激活
- 一旦兩個賣單被激活,其中一個被執行或者被取消,另一個都將被自動取消。
buy_bracket方法中除了設置買入價、止損價、止盈價外,還設置了訂單有效時間,如果訂單在有效時間內未執行,則會過期失效。一般我們將買入有效期設置為3天以內,賣出有效期設置為較長時間,以保證股票可以賣出。
回測000001後的最終資產為104779.07元,這裡交易大小僅為1000股,所用資金僅為不到20000元,提高交易量,收益還是相當可觀。
回測000002後的最終資產為97984.31元,微虧。
回測601318後的最終資產為87626.32元,虧損。
觀察幾張圖標可以發現,我們經常可以找到很好的買點,但是會出現本來盈利的訂單,最後變成虧損的情況。今天在挖backtrader文檔的時候,發現了一個比較好的利潤保護方法,計劃在下一篇文章中進行實現及分析,敬請期待。
友情提示:本系列學習筆記只做數據分析,記錄個人學習過程,不作為交易依據,盈虧自負。
連續下跌買入止盈止損賣出策略代碼:
<code>from
__future__ import (absolute_import, division, print_function, unicode_literals)
import
datetime # 用於datetime對象操作
import
os.path # 用於管理路徑
import
sys # 用於在argvTo[0]中找到腳本名稱
import
backtrader as bt # 引入backtrader框架
class
St(bt.Strategy):
params
=dict(
p_downdays
=4, # 連續下跌天數
p_stoploss
=0.05, # 止損比例
p_takeprofit
=0.1, # 止盈比例
limit
=0.005,
limdays
=3,
limdays2
=1000,
hold
=10,
usebracket
=False, # use order_target_size
switchp1p2
=False, # switch prices of order1 and order2
)
def
notify_order(self, order):
if
not order.alive() and order.ref in self.orefs:
self.orefs.remove(order.ref)
def
__init__(self):
=self.datas[0].close
sma
=bt.ind.SMA(period = self.p.p_downdays + 1, plot = False)
=list()
def
next(self):
if
self.orefs: # order列表,用於存儲尚未執行完成的訂單
return
# 有尚未執行的訂單
if
not self.position:
lastcloses
=list()
for
i in range(self.p.p_downdays + 1):
lastcloses.append(self.dataclose[-i])
if
lastcloses == sorted(lastcloses):
close
=self.dataclose[0]
p1
=close * (1.0 - self.p.limit)
p2
=p1 - self.p.p_stoploss * close
p3
=p1 + self.p.p_takeprofit * close
valid1
=datetime.timedelta(self.p.limdays)
valid2
=valid3 = datetime.timedelta(self.p.limdays2)
os
=self.buy_bracket(
price
=p1, valid=valid1,
stopprice
=p2, stopargs=dict(valid=valid2),
limitprice
=p3, limitargs=dict(valid=valid3),)
=[o.ref for o in os]
cerebro
=bt.Cerebro() # 創建cerebro
modpath
=os.path.dirname(os.path.abspath(sys.argv[0]))
datapath
=os.path.join(modpath, '../TQDat/day/stk/000002.csv')
data
=bt.feeds.GenericCSVData(
dataname
=datapath,
fromdate
=datetime.datetime(2018, 1, 1),
todate
=datetime.datetime(2020, 3, 31),
nullvalue
=0.0,
dtformat
=('%Y-%m-%d'),
datetime
=0,
open
=1,
high
=2,
low
=3,
close
=4,
volume
=5,
openinterest
=-1
)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
stake = 1000)
=0.001)
# 添加策略
# 遍歷所有數據
Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 繪圖
/<code>