python量化分析 03-订单状态说明

BackTrader对一些状态改变的通知是以回调的方式实现的,需要重写对回调函数的实现。目前支持以下通知:

  • notify_order(order):每次订单状态改变会触发回调
  • notify_trade(trade):任何开仓/更新/平仓交易的通知
  • notify_cashvalue(cash, value) :通知当前现金和投资组合
  • notify_store(msg, *args, **kwargs):关于存储的通知
  • notify_data(self, data, status, *args, **kwargs)::关于数据的通知
  • notify_timer(self, timer, when, *args, **kwargs):定时器通知,定时器可以通过成员函数add_timer()添加

订单状态值,及流程如下:

  • Order.Created 创建
  • Order.Submitted 提交给broker
  • Order.Accepted broker已接收
  • Order.Partial 订单部分被执行 order.executed查看订单
  • Order.Complete 订单已完成平均价格
  • Order.Rejected 被broker拒绝
  • Order.Margin 保证金不足\没有足够的现金执行订单。
  • Order.Cancelled 用户取消
  • Order.Expired 过期

order.size, order.price, order.pricelimit, order.ExecTypes[order.exectype], order.tradeid, order.Status[order.status]

order.Status订单的状态有:注:查阅订单为:order.Status[order.status] # Submitted
[‘Created’, ‘Submitted’, ‘Accepted’, ‘Partial’, ‘Completed’, ‘Canceled’, ‘Expired’, ‘Margin’, ‘Rejected’]

order.ExecTypes 的状态有:注:查阅为:order.ExecTypes[order.exectype]
[‘Market’, ‘Close’, ‘Limit’, ‘Stop’, ‘StopLimit’, ‘StopTrail’, ‘StopTrailLimit’, ‘Historical’]

trade.size, trade.price, trade.value, trade.tradeid, trade.status_names[trade.status]

trade.status_names状态有:注:查阅订单为:trade.status_names[trade.status] # Open
[‘Created’, ‘Open’, ‘Closed’]

self.order 状态有:

Ref: 25
OrdType: 0
OrdType: Buy
Status: 2
Status: Accepted
Size: 9282
Price: 12.1671
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 2
ExecType: Limit
CommInfo: None
End of Session: 737374.9999999999
Info: AutoOrderedDict()
Broker: None
Alive: True

判断在不在场内,如不在场,则可以进场

#检查是否有订单等待处理,如果是就不再进行其他下单
        if self.order:
            return
        # 检查是否已经进场
        if not self.position:
            # 还未进场,则只能进行买入

position用于表示当前的状态,由size和price两个成员属性构成,其中size表示当前持有资产数量,price表示当前价格,比如以16.90的价格买入1股平安银行,那么此时position.size=1,position.price=16.90。当把这1股平安银行卖出时,position.size=0,position.price=0。

trade表示一笔交易,当position.size由0变为X时,表示交易开启,当position.size由X变回0时,表示交易结束。代码中的trade.pnl表示当前这笔交易的在不算佣金的情况下的盈亏情况,trade.pnlcomm表示当前这笔交易的在计算佣金的情况下的盈亏情况,实际上trade.pnlcomm=trade.pnl-佣金。

import backtrader as bt    # 引入backtrader 框架
import datetime
import pandas as pd
import numpy as np
import os                 # 用于路径管理 数据导入时使用

# 创建策略
class TestStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        #日志函数
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    params = (('period', 30),)

    def __init__(self):
        self.sma = bt.indicators.SimpleMovingAverage(period=self.p.period)
        # self.order = None     #  如在定义 def start(self)函数内,则此处不需要
    def notify_order(self, order):
        print("\033[35m日期:{},订单通知 size:{},price:{},pricelimit:{},exectype:{},tradeid:{},status:{}\033[0m"
              .format(self.datas[0].datetime.date(0), order.size, order.price, order.pricelimit, order.ExecTypes[order.exectype],
                      order.tradeid, order.Status[order.status]))
        # self.log( 'BUY EXECUTED, Price: %.4f, Cost: %.4f, Comm %.4f' %
        #             (order.executed.price,
        #              order.executed.value,
        #              order.executed.comm))

        if not order.alive():
            self.order = None

    def notify_trade(self, trade):
        print("\033[32m日期:{},交易通知 size:{},price:{},value:{},tradeid:{},status:{}\033[0m"
              .format(self.datas[0].datetime.date(0), trade.size, trade.price, trade.value, trade.tradeid, trade.status_names[trade.status]))

    # def notify_cashvalue(self, cash, value):
    #     print("\033[33mbar序:{},资产通知 cash:{},value:{}\033[0m"
    #           .format(len(self),cash,value))
    # def notify_cashvalue(self, cash, value):
    #     print("\033[33mbar序:%s,资产通知 cash:%.2f,value:%.2f\033[0m"
    #           % (len(self),cash,value))

    def start(self):
        self.order = None

    def next(self):
        # print("bar序:{},next sma={},close={}".format(len(self),self.sma[0],self.data.close[0]))
        if self.order:
            return

        if not self.position:
            if self.sma > self.data.close:
                self.order = self.order_target_percent(target=1.0, price=self.data.close[0],
                                                       exectype=bt.Order.Limit)
                # self.order = self.buy(price=self.data.close[0]*0.99,exectype=bt.Order.Limit)
        else:
            if self.sma < self.data.close:
                self.close()

# 创建cerebro实例
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(TestStrategy)

# 添加策略开始和结束日期
start_time = datetime.datetime(2018, 1, 1)
end_time = datetime.datetime(2019, 12, 31)

# 先找到脚本文件的位置
modepath = os.path.dirname(__file__)
# 然后根据脚本与数据的相对关系找到数据位置,这样脚本从任意地方被调用,都可以正确地访问到数据
# datapath = modepath+'/data/000998.SZ.csv'
# datapath = os.path.join(modepath, './data/000998.SZ.csv')
datapath = os.path.join(modepath, '600000qfq.csv')

"""# data feeds 创建 通过GenericCSVData方式,Backtrader里使用的GenericCSVData函数,
要求CSV文件数据是按时间升序排列的。因此,如果股票数据未按时间升序排序,需要我们在做回测前对数据重新排序,否则会导致回测结果错误。
data = bt.feeds.GenericCSVData(
        dataname=datapath,
        fromdate=start_time,
        todate=end_time,
        nullvalue=0.0,
        dtformat='%Y-%m-%d',       # 自动把index数据中的符合日期的格式变成datetime类型
        datetime=0,
        open=2,
        high=3,
        low=4,
        close=5,
        volume=9,
        openinterest=-1
)"""

# 通过PandasData 方式导入dataframe数据,此方式较为直观推荐此种方案
"""直接在数据导入时,对列表名称处理好,usecols是抽取的列,header =0 取首行为列名,names进行改列名
在PandasData时,可以自载匹配数据"""
df = pd.read_csv(datapath,usecols=[2,3,4,5,6,10],header=0,
                 names=['datetime','open','high','low','close','volume'],
                 index_col=0,parse_dates=True)
df['openinterest'] = 0
df['ma5'] = 0
df['ma20'] = 20
# data = bt.feeds.PandasData(dataname=df,
#                            fromdate=start_time,
#                            todate=end_time)

# 拓展技术数据
class NewLines_PandasData(bt.feeds.PandasData):
    # 新增加两条数据线
    lines =('ma5','ma20',)
    # 新增数据在dataframe中的位置,最好使用-1进行自动匹配
    # -1表示自动按列明匹配数据,也可以设置为线在数据源中列的位置索引 (('pe',7),('pb',8),)
    params = (
                ('ma5',-1),
                ('ma20',-1),
              )

data =NewLines_PandasData(dataname=df,
                            fromdate=start_time,
                            todate=end_time)
# 在cerebro中添加价格数据
cerebro.adddata(data)
# 设置启动资金
cerebro.broker.setcash(100000.0)
# 打印开始资金
print('Starting Portfolio Value:%.2f' % cerebro.broker.getvalue())
# 遍历所有数据
cerebro.run()
# 打印最后结果
print('Final Portfolio Value:%.2f' % cerebro.broker.getvalue())

后续采用下述模版进行交易,思路清晰点:

import backtrader as bt    # 引入backtrader 框架
import datetime
import pandas as pd
import numpy as np
import os                 # 用于路径管理 数据导入时使用

# 创建策略
class TestStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        ''' 策略的日志函数'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
    def __init__(self):
        # 引用data[0]数据的收盘价数据
        self.dataclose = self.datas[0].close
        # 用于记录订单状态
        self.order = None
        self.buyprice = None
        self.buycomm = None
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # 提交给代理或者由代理接收的买/卖订单 - 不做操作
            return
        # 检查订单是否执行完毕
        # 注意:如果没有足够资金,代理会拒绝订单
        if order.status in [order.Completed]:
            if order.isbuy():
                print("\033[35m日期:{},BUY EXECUTED,Price:{:.2f},Cost:{:.2f},Comm:{:.2f}\033[0m"
                      .format(self.datas[0].datetime.date(0),
                              order.executed.price, order.executed.value,order.executed.comm))
                # self.log(
                #     'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                #     (order.executed.price,
                #      order.executed.value,
                #      order.executed.comm))

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else: # 卖
                print("\033[32m日期:{},SELL EXECUTED,Price:{:.2f},Cost:{:.2f},Comm:{:.2f}\033[0m"
                      .format(self.datas[0].datetime.date(0),
                              order.executed.price, order.executed.value,order.executed.comm))
                # self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                #          (order.executed.price,
                #           order.executed.value,
                #           order.executed.comm))
            self.bar_executed = len(self)
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            # self.log('Order Canceled/Margin/Rejected')
            print("\033[31m Order Canceled/Margin/Rejected\033[0m")
        # 订单状态处理完成,设为空
        self.order = None
        # 交易成果
    def notify_trade(self, trade):
        if not trade.isclosed:
            return
        # 显示交易的毛利率和净利润
        print("\033[31mOPERATION PROFIT, GROSS:{:.2f}, NET:{:.2f}\033[0m"
              .format(trade.pnl,trade.pnlcomm))
        # self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
        #          (trade.pnl, trade.pnlcomm))
    def next(self):
        # 日志输出收盘价数据
        # self.log('Close, %.2f' % self.dataclose[0])
        # 检查是否有订单等待处理,如果是就不再进行其他下单
        if self.order:
            return
        # 检查是否已经进场
        if not self.position:
            # 还未进场,则只能进行买入
            # 当日收盘价小于前一日收盘价
            if self.dataclose[0] < self.dataclose[-1]:
                    # 前一日收盘价小于前前日收盘价
                    if self.dataclose[-1] < self.dataclose[-2]:
                        # 买买买
                        self.log('BUY CREATE, %.2f' % self.dataclose[0])
                        # 记录订单避免二次下单
                        self.order = self.buy()
        # 如果已经在场内,则可以进行卖出操作
        else:
            # 卖卖卖
            if len(self) >= (self.bar_executed + 5):
                self.log('SELL CREATE, %.2f' % self.dataclose[0])
                # 记录订单避免二次下单
                self.order = self.sell()

# 创建cerebro实例
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(TestStrategy)

# 添加策略开始和结束日期
start_time = datetime.datetime(2018, 1, 1)
end_time = datetime.datetime(2019, 12, 31)

# 先找到脚本文件的位置
modepath = os.path.dirname(__file__)
# 然后根据脚本与数据的相对关系找到数据位置,这样脚本从任意地方被调用,都可以正确地访问到数据
# datapath = modepath+'/data/000998.SZ.csv'
# datapath = os.path.join(modepath, './data/000998.SZ.csv')
datapath = os.path.join(modepath, '600000qfq.csv')

"""# data feeds 创建 通过GenericCSVData方式,Backtrader里使用的GenericCSVData函数,
要求CSV文件数据是按时间升序排列的。因此,如果股票数据未按时间升序排序,需要我们在做回测前对数据重新排序,否则会导致回测结果错误。
data = bt.feeds.GenericCSVData(
        dataname=datapath,
        fromdate=start_time,
        todate=end_time,
        nullvalue=0.0,
        dtformat='%Y-%m-%d',       # 自动把index数据中的符合日期的格式变成datetime类型
        datetime=0,
        open=2,
        high=3,
        low=4,
        close=5,
        volume=9,
        openinterest=-1
)"""

# 通过PandasData 方式导入dataframe数据,此方式较为直观推荐此种方案
"""直接在数据导入时,对列表名称处理好,usecols是抽取的列,header =0 取首行为列名,names进行改列名
在PandasData时,可以自载匹配数据"""
df = pd.read_csv(datapath,usecols=[2,3,4,5,6,10],header=0,
                 names=['datetime','open','high','low','close','volume'],
                 index_col=0,parse_dates=True)
df['openinterest'] = 0
df['ma5'] = 0
df['ma20'] = 20
# data = bt.feeds.PandasData(dataname=df,
#                            fromdate=start_time,
#                            todate=end_time)

# 拓展技术数据
class NewLines_PandasData(bt.feeds.PandasData):
    # 新增加两条数据线
    lines =('ma5','ma20',)
    # 新增数据在dataframe中的位置,最好使用-1进行自动匹配
    # -1表示自动按列明匹配数据,也可以设置为线在数据源中列的位置索引 (('pe',7),('pb',8),)
    params = (
                ('ma5',-1),
                ('ma20',-1),
              )

data =NewLines_PandasData(dataname=df,
                            fromdate=start_time,
                            todate=end_time)
# 在cerebro中添加价格数据
cerebro.adddata(data)
# 设置启动资金
cerebro.broker.setcash(100000.0)
# 设置佣金率为千分之一
cerebro.broker.setcommission(commission=0.001)
# 打印开始资金
print('Starting Portfolio Value:%.2f' % cerebro.broker.getvalue())
# 遍历所有数据
cerebro.run()
# 打印最后结果
print('Final Portfolio Value:%.2f' % cerebro.broker.getvalue())


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值