天天看點

Python量化交易學習筆記(19)——連續下跌買入止盈止損賣出政策

好友提出要驗證連續下跌買入止盈止損賣出政策,本文對該政策回測和實作做分析記錄。

買入條件中,連續下跌定義為收盤價連續4日低于前1日的收盤價。賣出條件中,止盈率設定為10%,止損率設定為5%。回測初始資金100000元,單筆操作機關1000股,傭金千分之一,回測時間自2018年1月1日至2020年3月20日。

政策核心代碼位于政策類的next方法中:

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])
            # 連續N日下跌
            if lastcloses == sorted(lastcloses):
                # 計算買入報價跑p1,止損價p2,止盈價p3
                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)
                # 使用bracket orders設定買入賣出
                os = self.buy_bracket(
                    price=p1, valid=valid1,
                    stopprice=p2, stopargs=dict(valid=valid2),
                    limitprice=p3, limitargs=dict(valid=valid3),)
                # 儲存激活的的訂單
                self.orefs = [o.ref for o in os]      

這裡主要應用了backtrader的bracket order,它其實并不是1個訂單,而是有3個訂單構成:1個買單,1個止損賣單,1個止盈賣單。2個賣單就像是把買單用括号括起來一樣,是以合稱為bracket order(括号訂單)。我們所使用的buy_bracket方法遵循以下規則:

  • 為了避免被分開執行,3個訂單要被一起送出
  • 2個賣單要作為買單的子訂單
  • 在買單執行前,2個賣單處于非激活狀态
  • 買單如果被取消,2個賣單也會随之被取消
  • 買單被執行後,2個賣單均被激活
  • 一旦兩個賣單被激活,其中一個被執行或者被取消,另一個都将被自動取消。

buy_bracket方法中除了設定買入價、止損價、止盈價外,還設定了訂單有效時間,如果訂單在有效時間内未執行,則會過期失效。一般我們将買入有效期設定為3天以内,賣出有效期設定為較長時間,以保證股票可以賣出。

回測000001後的最終資産為102781.90元,這裡交易大小僅為1000股,所用資金僅為不到20000元,提高交易量,收益還是相當可觀。

Python量化交易學習筆記(19)——連續下跌買入止盈止損賣出政策

回測000002後的最終資産為96476.14元,虧損。

Python量化交易學習筆記(19)——連續下跌買入止盈止損賣出政策

回測601318後的最終資産為75044.50元,虧得一塌糊塗。

Python量化交易學習筆記(19)——連續下跌買入止盈止損賣出政策

觀察幾張圖示可以發現,我們經常可以找到很好的買點,但是會出現本來盈利的訂單,最後變成虧損的情況。在挖backtrader文檔的時候,發現了一個比較好的利潤保護方法,将在下一篇文章中進行實作及分析,敬請期待。

友情提示:本系列學習筆記隻做資料分析,記錄個人學習過程,不作為交易依據,盈虧自負。

連續下跌買入止盈止損賣出政策代碼:

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):
        print('{}: Order ref: {} / Type {} / Status {}'.format(
            self.data.datetime.date(0),
            order.ref, 'Buy' * order.isbuy() or 'Sell',
            order.getstatusname()))

        if order.status == order.Completed:
            self.holdstart = len(self)

        if not order.alive() and order.ref in self.orefs:
            self.orefs.remove(order.ref)
    def __init__(self):
        # 引用data[0]資料的收盤價資料
        self.dataclose = self.datas[0].close
        sma = bt.ind.SMA(period = self.p.p_downdays + 1, plot = False)
        self.orefs = 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])
            # 連續N日下跌
            if lastcloses == sorted(lastcloses):
                # 計算買入報價跑p1,止損價p2,止盈價p3
                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)
                # 使用bracket orders設定買入賣出
                os = self.buy_bracket(
                    price=p1, valid=valid1,
                    stopprice=p2, stopargs=dict(valid=valid2),
                    limitprice=p3, limitargs=dict(valid=valid3),)
                # 儲存激活的的訂單
                self.orefs = [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/000001.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中添加價格資料
cerebro.adddata(data)
# 設定啟動資金
cerebro.broker.setcash(100000.0)
# 設定交易機關大小
cerebro.addsizer(bt.sizers.FixedSize, stake = 1000)
# 設定傭金為千分之一
cerebro.broker.setcommission(commission=0.001)
cerebro.addstrategy(St)  # 添加政策
cerebro.run()  # 周遊所有資料
# 列印最後結果
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.plot()  # 繪圖