本文将對backtrader的倉位管理進行介紹,具體以同時回測交易3隻股票為例,檢視每日倉位情況。
政策
-
- 買入條件:5日線金叉60日線
- 賣出條件:5日線死叉60日線
示例
倉位資訊輸出的核心代碼位于政策類的next的方法中:
def next(self):
for i, d in enumerate(self.datas):
dt, dn = self.datetime.date(), d._name # 擷取時間及股票代碼
pos = self.getposition(d)
if not len(pos): # 不在場内,則可以買入
if self.inds[d]['cross'] > 0: # 如果金叉
self.buy(data = d, size = self.p.pstake) # 買買買
elif self.inds[d]['cross'] < 0: # 在場内,且死叉
self.close(data = d) # 賣賣賣
# 列印倉位資訊
print('**************************************************************', file = self.log_file)
print(self.data.datetime.date(), file = self.log_file)
for i, d in enumerate(self.datas):
pos = self.getposition(d)
if len(pos):
print('{}, 持倉:{}, 成本價:{}, 目前價:{}, 盈虧:{:.2f}'.format(
d._name, pos.size, pos.price, pos.adjbase, pos.size * (pos.adjbase - pos.price)),
file = self.log_file)
部分輸出結果為:
...
*************************************************************************************************
2019-06-24
000001, 持倉:100, 成本價:13.69, 目前價:13.69, 盈虧:0.00
*************************************************************************************************
2019-06-25
000001, 持倉:100, 成本價:13.69, 目前價:13.43, 盈虧:-26.00
*************************************************************************************************
2019-06-26
000001, 持倉:100, 成本價:13.69, 目前價:13.37, 盈虧:-32.00
*************************************************************************************************
2019-06-27
000001, 持倉:100, 成本價:13.69, 目前價:13.71, 盈虧:2.00
*************************************************************************************************
2019-06-28
000001, 持倉:100, 成本價:13.69, 目前價:13.78, 盈虧:9.00
*************************************************************************************************
2019-07-01
000001, 持倉:100, 成本價:13.69, 目前價:13.93, 盈虧:24.00
*************************************************************************************************
2019-07-02
000001, 持倉:100, 成本價:13.69, 目前價:14.18, 盈虧:49.00
*************************************************************************************************
2019-07-03
000001, 持倉:100, 成本價:13.69, 目前價:14.01, 盈虧:32.00
*************************************************************************************************
2019-07-04
000001, 持倉:100, 成本價:13.69, 目前價:13.99, 盈虧:30.00
*************************************************************************************************
2019-07-05
000001, 持倉:100, 成本價:13.69, 目前價:13.92, 盈虧:23.00
000002, 持倉:100, 成本價:29.45, 目前價:29.45, 盈虧:0.00
*************************************************************************************************
2019-07-08
000001, 持倉:100, 成本價:13.69, 目前價:13.59, 盈虧:-10.00
000002, 持倉:100, 成本價:29.45, 目前價:29.15, 盈虧:-30.00
...
輸出圖形為:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiATN381dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SOycjNzQTZ5cjNxIzN3U2YxYzX1ATMyETMxMzLcJTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
解析
上面的代碼用到了多隻股票同時進行政策回測,具體内容可以參見筆記(17)。
在backtrader中,使用類Position來管理倉位。Position的重要屬性包括:
-
- price:資産成本單價
- size:倉位大小
- adjbase:資産目前收盤價格
-
- 在類Strategy中,可以使用屬性position或者方法getposition(self, data=None, broker=None)來通路倉位資訊。
- 當使用getposition(self, data=None, broker=None)方法時,如果不指定參數data值,該方法會預設傳回datas[0]的倉位資訊,若指定參數data,則傳回對應資産的倉位資訊,如示例所示。
for i, d in enumerate(self.datas):
pos = self.getposition(d)
-
- 倉位資訊positions實際儲存在代理broker中,positions是一個Python的字典,key是一個種子資料Data Feed,value是一個類Position的對象。也就是說,positions[datas[0]]就表示第一隻股票datas[0]對應的倉位資訊,positions[datas[1]]就表示第二隻股票datas[1]對應的倉位資訊,依次類推。
- 類Position重寫了__len__方法,可以使用len(position)來判斷倉位大小是否為0。
- 類Position重寫了__str__方法,可以通過print(position)來列印倉位的具體資訊。
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架構
import pandas as pd
stk_num = 3 # 回測股票數目
# 建立政策
class SmaCross(bt.Strategy):
# 可配置政策參數
params = dict(
pfast=5, # 短期均線周期
pslow=60, # 長期均線周期
poneplot = False, # 是否列印到同一張圖
pstake = 100 # 單筆交易股票數目
)
def __init__(self):
self.log_file = open('position_log.txt', 'w') # 用于輸出倉位資訊
self.inds = dict()
for i, d in enumerate(self.datas):
self.inds[d] = dict()
self.inds[d]['sma1'] = bt.ind.SMA(d.close, period=self.p.pfast) # 短期均線
self.inds[d]['sma2'] = bt.ind.SMA(d.close, period=self.p.pslow) # 長期均線
self.inds[d]['cross'] = bt.ind.CrossOver(self.inds[d]['sma1'], self.inds[d]['sma2'], plot = False) # 交叉信号
# 跳過第一隻股票data,第一隻股票data作為主圖資料
if i > 0:
if self.p.poneplot:
d.plotinfo.plotmaster = self.datas[0]
def next(self):
for i, d in enumerate(self.datas):
dt, dn = self.datetime.date(), d._name # 擷取時間及股票代碼
pos = self.getposition(d)
if not len(pos): # 不在場内,則可以買入
if self.inds[d]['cross'] > 0: # 如果金叉
self.buy(data = d, size = self.p.pstake) # 買買買
elif self.inds[d]['cross'] < 0: # 在場内,且死叉
self.close(data = d) # 賣賣賣
# 列印倉位資訊
print('*****************************************************************************', file = self.log_file)
print(self.data.datetime.date(), file = self.log_file)
for i, d in enumerate(self.datas):
pos = self.getposition(d)
if len(pos):
print('{}, 持倉:{}, 成本價:{}, 目前價:{}, 盈虧:{:.2f}'.format(
d._name, pos.size, pos.price, pos.adjbase, pos.size * (pos.adjbase - pos.price)),
file = self.log_file)
def stop(self):
self.log_file.close()
pass
cerebro = bt.Cerebro() # 建立cerebro
# 讀入股票代碼
stk_code_file = '../TQDat/TQDown2020v1/data/tq_wrk_code2019.csv'
stk_pools = pd.read_csv(stk_code_file, encoding = 'gbk')
if stk_num > stk_pools.shape[0]:
print('股票數目不能大于%d' % stk_pools.shape[0])
exit()
for i in range(stk_num):
stk_code = stk_pools['code'][stk_pools.index[i]]
stk_code = '%06d' % stk_code
# 讀入資料
datapath = '../TQDat/day/stk/' + stk_code + '.csv'
# 建立價格資料
data = bt.feeds.GenericCSVData(
dataname = datapath,
fromdate = datetime.datetime(2019, 1, 1),
todate = datetime.datetime(2019, 12, 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, name = stk_code)
# 設定啟動資金
cerebro.broker.setcash(100000.0)
# 設定傭金為零
cerebro.broker.setcommission(commission=0.00)
cerebro.addstrategy(SmaCross, poneplot = False) # 添加政策
cerebro.run() # 周遊所有資料
# 列印最後結果
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.plot(style = "candlestick") # 繪圖