• QA_Order -- QUANTAXIS中的标准订单类

    • QA_Order是quantaxis的标准订单类, 产生于 QA_Account.send_order 这个接口

      QA_Order的属性
      QA_Order 的状态判断机制
      QA_Order的成交回调机制
      QA_Order的事件
      QA_Order的多市场兼容/接口兼容

  • 路径

    • QUANTAXIS/QUANTAXIS/QAMarket/QAOrder.py

QA_Order的属性

  • QA_Order 对象表示一个委托业务, 有如下字段
    • price 委托价格 (限价单用)
    • date 委托日期 (一般日线级别回测用)
    • datetime 当前时间 (分钟线级别和实时用)
    • sending_time 委托时间 (分钟线级别和实时用)
    • trade_time 成交时间 [list] (分钟/日线/实盘时用, 一笔订单多次成交会不断append进去)
    • amount 委托数量
    • frequence 频率 (回测用 DAY/1min/5min/15min/30min/...)
    • towards 买卖方向
    • code 订单的品种
    • user 订单发起者
    • account_cookie 订单发起账户的标识
    • strategy 策略号
    • order_model 委托方式(限价/市价/下一个bar/) type str eg 'limit'
    • money 订单金额
    • amount_model 委托量模式(按量委托/按总成交额委托) type str 'by_amount'
    • order_id 委托单id
    • trade_id 成交单id
    • _status 内部维护的订单状态
    • callback 当订单状态改变的时候 主动回调的函数(可以理解为自动执行的OnOrderAction)
    • commission_coeff 手续费系数
    • tax_coeff 印花税系数(股票)
    • exchange_id 交易所id (一般用于实盘期货)
  • param args type tuple
  • param kwargs type dict

QA_Order 的状态判断机制

In [ ]:
    @property
    def status(self):
        if self._status in [ORDER_STATUS.FAILED, ORDER_STATUS.NEXT, ORDER_STATUS.SETTLED, ORDER_STATUS.CANCEL_ALL, ORDER_STATUS.CANCEL_PART]:
            return self._status

        if self.pending_amount <= 0:
            self._status = ORDER_STATUS.SUCCESS_ALL
            return self._status
        elif self.pending_amount > 0 and self.trade_amount > 0:
            self._status = ORDER_STATUS.SUCCESS_PART
            return self._status
        elif self.trade_amount == 0:
            self._status = ORDER_STATUS.QUEUED
            return self._status
  • 惰性计算(Lazy Evaluation),又称懒惰计算、懒汉计算
    • 是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作
    • 通俗理解就是先不计算,等到需要的时候再来计算
    • 令人意外的是绝大多数的时候惰性计算将可以提高程序的性能
  • 在QA_Order中, 你想获取一个订单状态的时候, 会经历一个惰性计算的过程
    • 当订单属于
      • [ 委托失败 | 继续委托 | 已经结算 | 全部撤单 | 部分撤单 ] 状态
      • [ ORDER_STATUS.FAILED | ORDER_STATUS.NEXT | ORDER_STATUS.SETTLED | ORDER_STATUS.CANCEL_ALL | ORDER_STATUS.CANCEL_PART ]
      • ===> 直接返回状态结果
    • 当订单的 待成交数量[pending_amount] <=0 ===> 订单已经成交[ORDER_STATUS.SUCCESS_ALL]
    • 当订单的 待成交数量[pending_amount] >0 且 成交数量[trade_amount] >0 ===> 订单部分成交[ORDER_STATUS.SUCCESS_PART]
    • 当订单的 成交数量[trade_amount] ==0 ===> 订单扔在队列中[ORDER_STATUS.QUEUED]

QA_Order的成交回调机制

  • QA_Account 生成一个订单(接口: QA_Account.send_order)的时候, 会将QA_Account.receive_deal 的函数 作为回调句柄, 传给 QA_Ordercallback 函数
  • 因此, 当出现一笔交易的时候, QA_Order 会通过 Account.receive_deal 的方式自动回调告知账户成交
In [ ]:
# QA_Account 生成 QA_Order
_order = QA_Order(
      user_cookie = self.user_cookie
    , strategy = self.strategy_name
    , frequence = self.frequence
    , account_cookie = self.account_cookie
    , code = code
    , market_type = self.market_type
    , date = date
    , datetime = time
    , sending_time = time
    , callback = self.receive_deal
    , amount = amount
    , price = price
    , order_model = order_model
    , towards = towards
    , money = money
    , amount_model = amount_model
    , commission_coeff = self.commission_coeff
    , tax_coeff = self.tax_coeff
    , *args
    , **kwargs
)  # init
  • 其中 callback = self.receive_deal 该句中将 Account 的成交回调函数句柄的内存地址赋给了 QA_Order
  • 而 QA_Order 在成交了一笔订单以后, 则会调用 self.callback
In [ ]:
# QA_Order 的成交函数

self.trade_id.append(trade_id)

self.trade_price = (
    self.trade_price*self.trade_amount + trade_price*trade_amount) / (self.trade_amount + trade_amount
)

self.trade_amount += trade_amount

self.trade_time.append(trade_time)

self.callback(
      self.code
    , trade_id
    , self.order_id
    , self.realorder_id
    , trade_price
    , trade_amount
    , self.towards
    , trade_time
)

QA_Order的事件

  • create() 生成一个订单的时候调用
  • queued(realorder_id) 订单进入委托队列时使用(及 创建成功)
  • failed(reason=None) 订单创建失败时调用
  • trade(trade_id, trade_price, trade_amount, trade_time) 出现一笔成交单的时候调用
  • cancel() 撤单时调用
  • settle() 订单被结算事件调用时使用

QA_Order的多市场兼容/接口兼容

  • to_df
  • to_dict
  • to_otgformat
  • to_qatradegatway
  • from_otgformat
  • from_dict

源码

In [ ]:
# coding:utf-8
#
# The MIT License (MIT)
#
# Copyright (c) 2016-2021 yutiansut/QUANTAXIS
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import threading
import datetime
import pandas as pd

from QUANTAXIS.QAARP.market_preset import MARKET_PRESET
from QUANTAXIS.QAMarket.common import exchange_code
from QUANTAXIS.QAUtil import (
    QA_util_log_info,
    QA_util_random_with_topic,
    QA_util_to_json_from_pandas
)
from QUANTAXIS.QAUtil.QADate import QA_util_stamp2datetime
from QUANTAXIS.QAUtil.QAParameter import (
    AMOUNT_MODEL,
    MARKET_TYPE,
    ORDER_DIRECTION,
    ORDER_MODEL,
    ORDER_STATUS
)
"""
重新定义Order模式
在2017-12-15的Account-remake-version 分支中
Bid类全部被更名为Order类
用于和 bid_ask 区分
by yutiansut@2017/12/15
@2018/1/9
需要重新考虑 order的重复创建耗时问题?
order_frame 是一个管理性面板  但是还是需要一个缓存dict?
@2018/05/25
不建议保存两份变量, 维护起来很麻烦,容易出错。
"""


class QA_Order():
    '''
        记录order
    '''

    def __init__(
            self,
            price=None,
            date=None,
            datetime=None,
            sending_time=None,
            trade_time=False,
            amount=0,
            market_type=None,
            frequence=None,
            towards=None,
            code=None,
            user_cookie=None,
            account_cookie=None,
            strategy=None,
            order_model=None,
            money=None,
            amount_model=AMOUNT_MODEL.BY_AMOUNT,
            broker=None,
            order_id=None,
            trade_id=False,
            _status=ORDER_STATUS.NEW,
            callback=False,
            commission_coeff=0.00025,
            tax_coeff=0.001,
            exchange_id=None,
            position_id=None,
            *args,
            **kwargs
    ):
        '''
        QA_Order 对象表示一个委托业务, 有如下字段
        - price 委托价格 (限价单用)
        - date 委托日期 (一般日线级别回测用)
        - datetime 当前时间 (分钟线级别和实时用)
        - sending_time 委托时间 (分钟线级别和实时用)
        - trade_time 成交时间 [list] (分钟/日线/实盘时用, 一笔订单多次成交会不断append进去)
        - amount 委托数量
        - frequence 频率 (回测用 DAY/1min/5min/15min/30min/...)
        - towards 买卖方向
        - code  订单的品种
        - user_cookie  订单发起者
        - account_cookie 订单发起账户的标识
        - stratgy 策略号
        - order_model  委托方式(限价/市价/下一个bar/)  type str eg 'limit'
        - money  订单金额
        - amount_model 委托量模式(按量委托/按总成交额委托) type str 'by_amount'
        - order_id   委托单id
        - trade_id   成交单id
        - _status    内部维护的订单状态
        - callback   当订单状态改变的时候 主动回调的函数(可以理解为自动执行的OnOrderAction)
        - commission_coeff 手续费系数
        - tax_coeff  印花税系数(股票)
        - exchange_id  交易所id (一般用于实盘期货)
        :param args: type tuple
        :param kwargs: type dict
        # 2018-08-12 把order变成一个状态机>
        # 以前的order只是一个信息承载的工具,现在需要让他具备状态的方法
        NEW = 100
        SUCCESS_ALL = 200
        SUCCESS_PART = 203 # success_part 是部分成交 一个中间状态 剩余的订单还在委托队列中
        QUEUED = 300  # queued 用于表示在order_queue中 实际表达的意思是订单存活 待成交
        CANCEL = 400
        CANCEL_PART = 402 # cancel_part是部分撤单(及 下单后成交了一部分 剩余的被撤单 这是一个最终状态)
        SETTLED = 500
        FAILED = 600
        '''

        self.price = price
        self.datetime = None

        # 🛠todo 移动到 Util 类中 时间处理函数
        if datetime is None and date is not None:
            self.date = date
            self.datetime = '{} 09:31:00'.format(self.date)
        elif date is None and datetime is not None:
            self.date = datetime[0:10]
            self.datetime = datetime
        elif date is not None and datetime is not None:
            self.date = date
            self.datetime = datetime
        else:
            pass
        self.sending_time = self.datetime if sending_time is None else sending_time  # 下单时间

        self.trade_time = trade_time if trade_time else []  # 成交时间
        self.amount = amount                               # 委托数量
        self.trade_amount = 0                              # 成交数量
        self.cancel_amount = 0                             # 撤销数量
        self.towards = towards                             # side
        self.code = code                                   # 委托证券代码
        self.user_cookie = user_cookie                     # 委托用户
        self.market_type = market_type                     # 委托市场类别
        self.frequence = frequence                         # 委托所在的频率(回测用)
        self.account_cookie = account_cookie
        self.strategy = strategy
        self.type = market_type                            # see below
        self.order_model = order_model
        self.amount_model = amount_model
        self.order_id = QA_util_random_with_topic(
            topic='Order'
        ) if order_id is None else order_id
        self.realorder_id = self.order_id
        self.commission_coeff = commission_coeff
        self.tax_coeff = tax_coeff
        self.trade_id = trade_id if trade_id else []
        self.market_preset = MARKET_PRESET().get_code(self.code)

        self.trade_price = 0                                       # 成交均价
        self.broker = broker
        self.callback = callback                                   # 委托成功的callback
        self.money = money                                         # 委托需要的金钱
        self.reason = None                                         # 原因列表
        self.exchange_id = exchange_id
        self.time_condition = 'GFD'                                # 当日有效
        self._status = _status
        self.exchange_code = exchange_code
        self.position_id = position_id
        # 增加订单对于多账户以及多级别账户的支持 2018/11/12
        self.mainacc_id = None if 'mainacc_id' not in kwargs.keys(
        ) else kwargs['mainacc_id']
        self.subacc_id = None if 'subacc_id' not in kwargs.keys(
        ) else kwargs['subacc_id']
        self.direction = 'BUY' if self.towards in [
            ORDER_DIRECTION.BUY,
            ORDER_DIRECTION.BUY_OPEN,
            ORDER_DIRECTION.BUY_CLOSE
        ] else 'SELL'
        self.offset = 'OPEN' if self.towards in [
            ORDER_DIRECTION.BUY,
            ORDER_DIRECTION.BUY_OPEN,
            ORDER_DIRECTION.SELL_OPEN
        ] else 'CLOSE'

    @property
    def pending_amount(self):
        return self.amount - self.cancel_amount - self.trade_amount

    @property
    def __dict__(self):
        return {
            'price': self.price,
            'datetime': self.datetime,
            'date': self.date,
            'sending_time': self.sending_time,
            'trade_time': self.trade_time,
            'amount': self.amount,
            'trade_amount': self.trade_amount,
            'cancel_amount': self.cancel_amount,
            'towards': self.towards,
            'code': self.code,
            'user_cookie': self.user_cookie,
            'market_type': self.market_type,
            'frequence': self.frequence,
            'account_cookie': self.account_cookie,
            'strategy': self.strategy,
            'type': self.market_type,
            'order_model': self.order_model,
            'amount_model': self.amount_model,
            'order_id': self.order_id,
            'realorder_id': self.realorder_id,
            'commission_coeff': self.commission_coeff,
            'tax_coeff': self.tax_coeff,
            'trade_id': self.trade_id,
            'trade_price': self.trade_price,
            'broker': self.broker,
            'callback': self.callback,
            'money': self.money,
            'reason': self.reason,
            'exchange_id': self.exchange_id,
            'time_condition': self.time_condition,
            '_status': self.status,
            'direction': self.direction,
            'offset': self.offset
        }

    def __repr__(self):
        '''
        输出格式化对象
        :return:  字符串
        '''
        return '< QA_Order realorder_id {} datetime:{} code:{} amount:{} price:{} towards:{} btype:{} order_id:{} account:{} status:{} >'.format(
            self.realorder_id,
            self.datetime,
            self.code,
            self.amount,
            self.price,
            self.towards,
            self.type,
            self.order_id,
            self.account_cookie,
            self.status
        )

    def transform_dt(self, times):
        if isinstance(times, str):
            tradedt = datetime.datetime.strptime(times, '%Y-%m-%d %H:%M:%S') if len(times) == 19 else datetime.datetime.strptime(times, '%Y-%m-%d %H:%M:%S.%f')
            return tradedt.timestamp()*1000000000
        elif isinstance(times, datetime.datetime):
            return tradedt.timestamp()*1000000000

    @property
    def status(self):

        # 以下几个都是最终状态 并且是外部动作导致的
        if self._status in [ORDER_STATUS.FAILED,
                            ORDER_STATUS.NEXT,
                            ORDER_STATUS.SETTLED,
                            ORDER_STATUS.CANCEL_ALL,
                            ORDER_STATUS.CANCEL_PART]:
            return self._status

        if self.pending_amount <= 0:
            self._status = ORDER_STATUS.SUCCESS_ALL
            return self._status
        elif self.pending_amount > 0 and self.trade_amount > 0:
            self._status = ORDER_STATUS.SUCCESS_PART
            return self._status
        elif self.trade_amount == 0:
            self._status = ORDER_STATUS.QUEUED
            return self._status

    def calc_commission(self, trade_price, trade_amount):

        if self.market_type == MARKET_TYPE.FUTURE_CN:
            value = trade_price * trade_amount * \
                self.market_preset.get('unit_table', 1)
            if self.towards in [ORDER_DIRECTION.BUY_OPEN,
                                ORDER_DIRECTION.BUY_CLOSE,
                                ORDER_DIRECTION.SELL_CLOSE,
                                ORDER_DIRECTION.SELL_OPEN]:
                commission_fee = self.market_preset['commission_coeff_pervol'] * trade_amount + \
                    self.market_preset['commission_coeff_peramount'] * \
                    abs(value)
            elif self.towards in [ORDER_DIRECTION.BUY_CLOSETODAY,
                                  ORDER_DIRECTION.SELL_CLOSETODAY]:
                commission_fee = self.market_preset['commission_coeff_today_pervol'] * trade_amount + \
                    self.market_preset['commission_coeff_today_peramount'] * \
                    abs(value)
            return commission_fee
        elif self.market_type == MARKET_TYPE.STOCK_CN:
            commission_fee = trade_price * trade_amount * self.commission_coeff

            return max(commission_fee, 5)

    def get_exchange(self, code):
        return self.exchange_code.get(code.lower(), 'Unknown')

    def create(self):
        """创建订单
        """
        # 创建一笔订单(未进入委托队列-- 在创建的时候调用)
        self._status = ORDER_STATUS.NEW

    def cancel(self):
        """撤单
        Arguments:
            amount {int} -- 撤单数量
        """

        self.cancel_amount = self.amount - self.trade_amount
        if self.trade_amount == 0:
            # 未交易  直接订单全撤
            self._status = ORDER_STATUS.CANCEL_ALL
        else:
            # 部分交易 剩余订单全撤
            self._status = ORDER_STATUS.CANCEL_PART

    def failed(self, reason=None):
        """失败订单(未成功创建入broker)
        Arguments:
            reason {str} -- 失败原因
        """
        # 订单创建失败(如废单/场外废单/价格高于涨停价/价格低于跌停价/通讯失败)
        self._status = ORDER_STATUS.FAILED
        self.reason = str(reason)

    def trade(self, trade_id, trade_price, trade_amount, trade_time):
        """trade 状态
        Arguments:
            amount {[type]} -- [description]
        """
        if self.status in [ORDER_STATUS.SUCCESS_PART, ORDER_STATUS.QUEUED]:
            trade_amount = int(trade_amount)
            trade_id = str(trade_id)

            if trade_amount < 1:

                self._status = ORDER_STATUS.NEXT
                return False
            else:
                if trade_id not in self.trade_id:
                    trade_price = float(trade_price)

                    trade_time = str(trade_time)

                    self.trade_id.append(trade_id)
                    self.trade_price = (
                        self.trade_price * self.trade_amount +
                        trade_price * trade_amount
                    ) / (
                        self.trade_amount + trade_amount
                    )
                    self.trade_amount += trade_amount
                    self.trade_time.append(trade_time)
                    res = self.callback(
                        self.code,
                        trade_id,
                        self.order_id,
                        self.realorder_id,
                        trade_price,
                        trade_amount,
                        self.towards,
                        trade_time
                    )
                    if res == 0:
                        return self.trade_message(
                            trade_id,
                            trade_price,
                            trade_amount,
                            trade_time
                        )
                    else:
                        return False
                else:
                    return False
        else:
            print(
                RuntimeError(
                    'ORDER STATUS {} CANNNOT TRADE'.format(self.status)
                )
            )
            return False

    def trade_message(self, trade_id, trade_price, trade_amount, trade_time):
        return {
            "user_id": self.account_cookie,  # //用户ID
            "order_id": self.order_id,  # //交易所单号
            "trade_id": trade_id,  # //委托单ID, 对于一个USER, trade_id 是永远不重复的
            "exchange_id": self.exchange_id,  # //交易所
            "instrument_id": self.code,  # //在交易所中的合约代码
            "exchange_trade_id": trade_id,  # //交易所单号
            "direction": self.direction,  # //下单方向
            "offset": self.offset,  # //开平标志
            "volume": trade_amount,  # //成交手数
            "price": trade_price,  # //成交价格
            "trade_date_time":  trade_time,  # //成交时间, epoch nano
            # //成交手续费
            "commission": self.calc_commission(trade_price, trade_amount),
            "seqno": ''}

    def queued(self, realorder_id):
        self.realorder_id = realorder_id
        self._status = ORDER_STATUS.QUEUED

    def settle(self):
        self._status = ORDER_STATUS.SETTLED

    def get(self, key, exception=None):
        try:
            if key is None:
                print("key is none , return none!")
                return None
            return eval('self.{}'.format(key))
        except Exception as e:
            return exception

    # 🛠todo 建议取消,直接调用var

    def callingback(self):
        """回调函数
        Returns:
            [type] -- [description]
        """

        if self.callback:
            return self.callback

    def info(self):
        '''
        :return:
        '''
        return vars(self)

    # 对象转变成 dfs
    def to_df(self):
        return pd.DataFrame([
            vars(self),
        ])

    # 🛠todo 建议取消,直接调用var?

    def to_dict(self):
        '''
        把对象中的属性转变成字典类型
        :return: dict
        '''
        return vars(self)

    def to_otgdict(self):
        """{
                "aid": "insert_order",                  # //必填, 下单请求
                # //必填, 需要与登录用户名一致, 或为登录用户的子账户(例如登录用户为user1, 则报单 user_id 应当为 user1 或 user1.some_unit)
                "user_id": account_cookie,
                # //必填, 委托单号, 需确保在一个账号中不重复, 限长512字节
                "order_id": order_id if order_id else QA.QA_util_random_with_topic('QAOTG'),
                "exchange_id": exchange_id,  # //必填, 下单到哪个交易所
                "instrument_id": code,               # //必填, 下单合约代码
                "direction": order_direction,                      # //必填, 下单买卖方向
                # //必填, 下单开平方向, 仅当指令相关对象不支持开平机制(例如股票)时可不填写此字段
                "offset":  order_offset,
                "volume":  volume,                             # //必填, 下单手数
                "price_type": "LIMIT",  # //必填, 报单价格类型
                "limit_price": price,  # //当 price_type == LIMIT 时需要填写此字段, 报单价格
                "volume_condition": "ANY",
                "time_condition": "GFD",
            }
        """
        return {
            "aid": "insert_order",                  # //必填, 下单请求
            # //必填, 需要与登录用户名一致, 或为登录用户的子账户(例如登录用户为user1, 则报单 user_id 应当为 user1 或 user1.some_unit)
            "user_id": self.account_cookie,
            # //必填, 委托单号, 需确保在一个账号中不重复, 限长512字节
            "order_id": self.order_id,
            "exchange_id": self.exchange_id,  # //必填, 下单到哪个交易所
            "instrument_id": self.code,               # //必填, 下单合约代码
            "direction": self.direction,                      # //必填, 下单买卖方向
            # //必填, 下单开平方向, 仅当指令相关对象不支持开平机制(例如股票)时可不填写此字段
            "offset":  self.offset,
            "volume":  self.amount,                             # //必填, 下单手数
            "price_type": self.order_model,  # //必填, 报单价格类型
            "limit_price": self.price,  # //当 price_type == LIMIT 时需要填写此字段, 报单价格
            "volume_condition": "ANY",
            "time_condition": "GFD",
        }

    def to_qatradegatway(self):
        """[summary]
        {'topic': 'sendorder', 
        'account_cookie': '100004', 
        'strategy_id': None, 
        'order_direction': 'SELL', 
        'order_offset': 'OPEN', 
        'code': 'rb1910', 
        'price': 3745.0, 
        'order_time': '2019-05-08 13:55:38.000000', 
        'exchange_id': 'SHFE', 
        'volume': 1.0, 
        'order_id': '5ab55219-adf6-432f-90db-f1bc5f29f4e5'}
        'topic': 'sendorder',
        'account_cookie': acc,
        'strategy_id': 'test',
        'code': code,
        'price': price[code],
        'order_direction': 'SELL',
        'order_offset': 'CLOSE',
        'volume': 1,
        'order_time': str(datetime.datetime.now()),
        'exchange_id': 'SHFE'
        """
        return {
            'topic': 'sendorder',
            'account_cookie': self.account_cookie,
            'strategy_id': self.strategy,
            'order_direction': self.direction,
            'order_offset': self.offset,
            'code': self.code.lower(),
            'price': self.price,
            'order_time': self.sending_time,
            'exchange_id': self.get_exchange(self.code),
            'volume': int(self.amount),
            'order_id': self.order_id
        }

    def to_qifi(self):

        return {
            "account_cookie": self.account_cookie,
            "user_id": self.account_cookie,
            "instrument_id": self.code,
            "towards": self.towards,
            "exchange_id": self.exchange_id,
            "order_time": self.datetime,
            "volume": self.amount,
            "price": self.price,
            "order_id": self.order_id,
            "seqno": 1,
            "direction": self.direction,                      # //
            "offset": self.offset,  # //
            "volume_orign": self.amount,
            "price_type": self.order_model,
            "limit_price": self.price,
            "time_condition": "GFD",
            "volume_condition": "ANY",
            "insert_date_time": self.transform_dt(self.datetime),
            "exchange_order_id": self.realorder_id,
            "status": self.status,
            "volume_left": self.pending_amount,
            "last_msg": "",
            "topic": "send_order"
        }

    def from_otgformat(self, otgOrder):
        """[summary]
        Arguments:
            otgOrder {[type]} -- [description]
        {'seqno': 6,
        'user_id': '106184',
        'order_id': 'WDRB_QA01_FtNlyBem',
        'exchange_id': 'SHFE',
        'instrument_id': 'rb1905',
        'direction': 'SELL',
        'offset': 'OPEN',
        'volume_orign': 50, #(总报单手数)
        'price_type': 'LIMIT', # "LIMIT" (价格类型, ANY=市价, LIMIT=限价)
        'limit_price': 3432.0, # 4500.0 (委托价格, 仅当 price_type = LIMIT 时有效)
        'time_condition': 'GFD',#  "GFD" (时间条件, IOC=立即完成,否则撤销, GFS=本节有效, GFD=当日有效, GTC=撤销前有效, GFA=集合竞价有效)
        'volume_condition': 'ANY', # "ANY" (手数条件, ANY=任何数量, MIN=最小数量, ALL=全部数量)
        'insert_date_time': 1545656460000000000,# 1501074872000000000 (下单时间(按北京时间),自unix epoch(1970-01-01 00:00:00 GMT)以来的纳秒数)
        'exchange_order_id': '        3738',
        'status': 'FINISHED', # "ALIVE" (委托单状态, ALIVE=有效, FINISHED=已完)
        'volume_left': 0,
        'last_msg': '全部成交报单已提交'} # "报单成功" (委托单状态信息)
        """
        self.order_id = otgOrder.get('order_id')
        self.account_cookie = otgOrder.get('user_id')
        self.exchange_id = otgOrder.get('exchange_id')
        self.code = str(otgOrder.get('instrument_id')).upper()
        self.offset = otgOrder.get('offset')
        self.direction = otgOrder.get('direction')
        self.towards = eval(
            'ORDER_DIRECTION.{}_{}'.format(self.direction,
                                           self.offset)
        )
        self.amount = otgOrder.get('volume_orign')
        self.trade_amount = self.amount - otgOrder.get('volume_left')
        self.price = otgOrder.get('limit_price')
        self.order_model = eval(
            'ORDER_MODEL.{}'.format(otgOrder.get('price_type'))
        )
        self.time_condition = otgOrder.get('time_condition')
        if otgOrder.get('insert_date_time') == 0:
            self.datetime = 0
        else:
            self.datetime = QA_util_stamp2datetime(
                int(otgOrder.get('insert_date_time'))
            )
        self.sending_time = self.datetime
        self.volume_condition = otgOrder.get('volume_condition')
        self.message = otgOrder.get('last_msg')

        self._status = ORDER_STATUS.NEW
        if '拒绝' in self.message or '仓位不足' in self.message:
            # 仓位不足:  一般是平今/平昨仓位不足
            self._status = ORDER_STATUS.FAILED
        if '已撤单' in self.message:
            self._status = ORDER_STATUS.CANCEL_ALL
        self.realorder_id = otgOrder.get('exchange_order_id')
        return self

    def from_dict(self, order_dict):
        '''
        从字段类型的字段 填充 对象的字段
        :param order_dict:  dict 类型
        :return: self QA_Order
        '''
        try:
            # QA_util_log_info('QA_ORDER CHANGE: from {} change to {}'.format(
            #     self.order_id, order['order_id']))
            self.price = order_dict['price']
            self.date = order_dict['date']
            self.datetime = order_dict['datetime']
            self.sending_time = order_dict['sending_time']  # 下单时间
            self.trade_time = order_dict['trade_time']
            self.amount = order_dict['amount']
            self.frequence = order_dict['frequence']
            self.market_type = order_dict['market_type']
            self.towards = order_dict['towards']
            self.code = order_dict['code']
            self.user_cookie = order_dict['user_cookie']
            self.account_cookie = order_dict['account_cookie']
            self.strategy = order_dict['strategy']
            self.type = order_dict['type']
            self.order_model = order_dict['order_model']
            self.amount_model = order_dict['amount_model']
            self.order_id = order_dict['order_id']
            self.realorder_id = order_dict['realorder_id']
            self.trade_id = order_dict['trade_id']
            self.callback = order_dict['callback']
            self.commission_coeff = order_dict['commission_coeff']
            self.tax_coeff = order_dict['tax_coeff']

            self.money = order_dict['money']
            self._status = order_dict['_status']

            self.cancel_amount = order_dict['cancel_amount']
            self.trade_amount = order_dict['trade_amount']
            self.trade_price = order_dict['trade_price']
            self.reason = order_dict['reason']

            return self
        except Exception as e:
            QA_util_log_info('Failed to tran from dict {}'.format(e))


class QA_OrderQueue():  # also the order tree ?? what's the tree means?
    """
    一个待成交队列
    queue是一个dataframe
    这里面都是对于方法的封装
    queue_df 的意图
    对orderqueue进行管理 这是一个dataframe
    然后等到要恢复订单的时候 再去用orderid恢复他
    就好比 你下了个单子
    你就在小本本上记一笔
    然后成交了你打个勾
    撤单了你打个叉
    你看看你还有多少单子在委托你就数数小本子
    这个小本子 就是orderqueue的dataframe
    """

    def __init__(self):
        """重新修改 优化性能
        1. 维护两个dict
           order_list 是一天的所有订单
           deal_list 是历史的成交单(settle以后 , 把order_list append进去)
        """

        self.order_list = {}
        self.deal_list = {}

    def __repr__(self):
        return '< QA_OrderQueue >'
        # return '< QA_OrderQueue AMOUNT {} WAITING TRADE {} >'.format(len(self.queue_df), len(self.pending))

    def __call__(self):
        return self.order_list

    def insert_order(self, order):
        '''
        :param order: QA_Order类型
        :return:
        '''
        #print("     *>> QAOrder!insert_order  {}".format(order))
        # QUEUED = 300  # queued 用于表示在order_queue中 实际表达的意思是订单存活 待成交
        #order.status = ORDER_STATUS.QUEUED
        # 🛠 todo 是为了速度快把order对象转换成 df 对象的吗?
        #self.queue_df = self.queue_df.append(order.to_df(), ignore_index=True)
        #self.queue_df.set_index('order_id', drop=True, inplace=True)
        if order is not None:
            self.order_list[order.order_id] = order
            return order
        else:
            print('QAERROR Wrong for get None type while insert order to Queue')

    def update_order(self, order):
        if self.order_list[order.order_id].status != order.status:
            self.order_list[order.order_id] = order
            return True
        else:
            if self.order_list[order.order_id
                               ].trade_amount != order.trade_amount:
                slef.order_list[order.order_id] = order
                return True
            else:
                return False

    @property
    def order_ids(self):
        return list(self.order_list.keys())

    @property
    def len(self):
        return len(self.order_list)

    def settle(self):
        """结算
        清空订单簿
        """
        self.deal_list.update(self.order_list)
        self.order_list = {}

    @property
    def pending(self):
        '''
        600 废单 未委托成功
        200 委托成功,完全交易
        203 委托成功,未完全成功
        300 委托队列 待成交
        400 已撤单
        500 服务器撤单/每日结算
        订单生成(100) -- 废单(600)
        订单生成(100) -- 进入待成交队列(300) -- 完全成交(200) -- 每日结算(500)-- 死亡
        订单生成(100) -- 进入待成交队列(300) -- 部分成交(203) -- 未成交(300) -- 每日结算(500) -- 死亡
        订单生成(100) -- 进入待成交队列(300) -- 主动撤单(400) -- 每日结算(500) -- 死亡
        选择待成交列表
        :return: dataframe
        '''
        try:
            return [
                item for item in self.order_list.values() if item.status in [
                    ORDER_STATUS.QUEUED,
                    ORDER_STATUS.NEXT,
                    ORDER_STATUS.SUCCESS_PART
                ]
            ]
        except:
            return []

    @property
    def failed(self):
        try:
            return [
                item for item in self.order_list.values()
                if item.status in [ORDER_STATUS.FAILED]
            ]
        except:
            return []

    @property
    def canceled(self):
        try:
            return [
                item for item in self.order_list.values() if item.status in
                [ORDER_STATUS.CANCEL_ALL,
                 ORDER_STATUS.CANCEL_PART]
            ]
        except:
            return []

    @property
    def untrade(self):
        try:
            return [
                item for item in self.order_list.values()
                if item.status in [ORDER_STATUS.QUEUED]
            ]
        except:
            return []

    # 🛠todo 订单队列

    def set_status(self, order_id, new_status):
        try:
            if order_id in self.order_ids:

                self.order_list[order_id].status = new_status
            else:
                pass
        except:
            return None

    def to_df(self):
        try:
            return pd.concat([x.to_df() for x in self.order_list.values()])
        except:
            pass

    @property
    def order_qifi(self):
        return dict(zip(self.order_list.keys(), [item.to_qifi() for item in self.order_list.values()]))


if __name__ == '__main__':
    ax = QA_Order()

    print(ax.info())
    print(ax.to_df())