Example #1
0
class QIFI_Account():
    def __init__(self,
                 username,
                 password,
                 model="SIM",
                 broker_name="QAPaperTrading",
                 trade_host=mongo_ip,
                 init_cash=1000000,
                 taskid=str(uuid.uuid4())):
        """Initial
        QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类


        1. 兼容多持仓组合
        2. 动态计算权益

        使用 model = SIM/ REAL来切换

        qifiaccount 不去区分你的持仓是股票还是期货, 因此你可以实现跨市场的交易持仓管理
        """
        self.user_id = username
        self.username = username
        self.password = password

        self.source_id = "QIFI_Account"  # 识别号
        self.market_preset = MARKET_PRESET()
        # 指的是 Account所属的账户编组(实时的时候的账户观察组)
        self.portfolio = "QAPaperTrade"
        self.model = model

        self.broker_name = broker_name  # 所属期货公司/ 模拟的组
        self.investor_name = ""  # 账户所属人(实盘的开户人姓名)
        self.bank_password = ""
        self.capital_password = ""
        self.wsuri = ""

        self.bank_id = "QASIM"
        self.bankname = "QASIMBank"

        self.trade_host = trade_host
        if model == 'BACKTEST':
            self.db = pymongo.MongoClient(trade_host).quantaxis
        else:
            self.db = pymongo.MongoClient(trade_host).QAREALTIME

        self.pub_host = ""
        self.trade_host = ""
        self.last_updatetime = ""
        self.status = 200
        self._trading_day = ""
        self.init_cash = init_cash
        self.pre_balance = 0
        self.datetime = ""
        self.static_balance = 0

        self.deposit = 0  # 入金
        self.withdraw = 0  # 出金
        self.withdrawQuota = 0  # 可取金额
        self.close_profit = 0
        self.premium = 0  # 本交易日内交纳的期权权利金
        self.event_id = 0
        self.taskid = taskid
        self.money = 0
        # QIFI 协议
        self.transfers = {}
        self.schedule = {}

        self.banks = {}

        self.frozen = {}

        self.event = {}
        self.positions = {}
        self.trades = {}
        self.orders = {}

    def initial(self):

        self.reload()

        if self.pre_balance == 0 and self.balance == 0 and self.model != "REAL":
            self.log('Create new Account')
            self.create_simaccount()

        self.sync()

    @property
    def trading_day(self):
        if self.model == "BACKTEST":
            return str(self.datetime)[0:10]
        else:
            return self._trading_day

    def reload(self):
        if self.model.upper() in ['REAL', 'SIM']:
            message = self.db.account.find_one({
                'account_cookie': self.user_id,
                'password': self.password
            })

            time = datetime.datetime.now()
            # resume/settle

            if time.hour <= 15:
                self._trading_day = time.date()
            else:
                if time.weekday() in [0, 1, 2, 3]:
                    self._trading_day = time.date() + datetime.timedelta(
                        days=1)
                elif time.weekday() in [4, 5, 6]:
                    self._trading_day = time.date() + datetime.timedelta(
                        days=(7 - time.weekday()))
            if message is not None:
                accpart = message.get('accounts')

                self.money = message.get('money')
                self.source_id = message.get('sourceid')

                self.pre_balance = accpart.get('pre_balance')
                self.deposit = accpart.get('deposit')
                self.withdraw = accpart.get('withdraw')
                self.withdrawQuota = accpart.get('WithdrawQuota')
                self.close_profit = accpart.get('close_profit')
                self.static_balance = accpart.get('static_balance')
                self.event = message.get('event')
                self.trades = message.get('trades')
                self.transfers = message.get('transfers')
                self.orders = message.get('orders')
                self.taskid = message.get('taskid', str(uuid.uuid4()))

                positions = message.get('positions')
                for position in positions.values():
                    self.positions[position.get(
                        'instrument_id')] = QA_Position().loadfrommessage(
                            position)

                for order in self.open_orders:
                    self.log('try to deal {}'.format(order))
                    self.make_deal(order)

                self.banks = message.get('banks')

                self.status = message.get('status')
                self.wsuri = message.get('wsuri')

                self.on_reload()

                if message.get('trading_day', '') == str(self._trading_day):
                    # reload
                    pass

                else:
                    # settle
                    self.settle()

    def create_fromQIFI(self, message):
        pass

    def sync(self):
        self.on_sync()
        if self.model == "BACKTEST":
            ## 数据库: quantaxis.history
            self.db.history.update(
                {
                    'account_cookie': self.user_id,
                    'trading_day': self.trading_day
                }, {'$set': self.message},
                upsert=True)
        else:
            ## 数据库: QAREALTIME.account
            self.db.account.update(
                {
                    'account_cookie': self.user_id,
                    'password': self.password
                }, {'$set': self.message},
                upsert=True)

            self.db.hisaccount.insert_one({
                'updatetime': self.dtstr,
                'account_cookie': self.user_id,
                'accounts': self.account_msg
            })

    def settle(self):
        self.log('settle')
        if self.model == "BACKTEST":
            ## 数据库: quantaxis.history
            self.db.history.update(
                {
                    'account_cookie': self.user_id,
                    'trading_day': self.trading_day
                }, {'$set': self.message},
                upsert=True)
        else:
            self.db.hisaccount.update_one(
                {
                    'account_cookie': self.user_id,
                    'trading_day': self.trading_day
                }, {'$set': self.message},
                upsert=True)
        self.pre_balance += (self.deposit - self.withdraw + self.close_profit)
        self.static_balance = self.pre_balance

        self.close_profit = 0
        self.deposit = 0  # 入金
        self.withdraw = 0  # 出金
        self.premium = 0
        self.money += self.frozen_margin

        self.orders = {}
        self.frozen = {}
        self.trades = {}
        self.transfers = {}
        self.event = {}
        self.event_id = 0

        for item in self.positions.values():
            item.settle()

        # sell first >> second buy ==> for make sure have enough cash
        buy_order_sche = []
        for order in self.schedule.values():
            if order['towards'] > 0:
                # buy order
                buy_order_sche.append(order)
            else:
                self.send_order(order['code'], order['amount'], order['price'],
                                order['towards'], order['order_id'])
        for order in buy_order_sche:
            self.send_order(order['code'], order['amount'], order['price'],
                            order['towards'], order['order_id'])
        self.schedule = {}

    def on_sync(self):
        pass

    def on_reload(self):
        pass

    @property
    def dtstr(self):
        return str(datetime.datetime.now()).replace('.', '_')

    def ask_deposit(self, money):

        self.deposit += money
        self.money += money
        self.transfers[str(self.event_id)] = {
            "datetime": 433241234123,  # // 转账时间, epoch nano
            "currency": "CNY",  # 币种
            "amount": money,  # 涉及金额
            "error_id": 0,  # 转账结果代码
            "error_msg": "成功",  # 转账结果代码
        }
        self.event[self.dtstr] = "转账成功 {}".format(money)

    def ask_withdraw(self, money):
        if self.withdrawQuota > money:
            self.withdrawQuota -= money
            self.withdraw += money
            self.transfers[str(self.event_id)] = {
                "datetime": 433241234123,  # // 转账时间, epoch nano
                "currency": "CNY",  # 币种
                "amount": -money,  # 涉及金额
                "error_id": 0,  # 转账结果代码
                "error_msg": "成功",  # 转账结果代码
            }
            self.event[self.dtstr] = "转账成功 {}".format(-money)
        else:
            self.event[self.dtstr] = "转账失败: 余额不足 left {}  ask {}".format(
                self.withdrawQuota, money)

    def create_simaccount(self):
        self._trading_day = str(datetime.date.today())
        self.wsuri = "ws://www.yutiansut.com:7988"
        self.pre_balance = 0
        self.static_balance = 0
        self.deposit = 0  # 入金
        self.withdraw = 0  # 出金
        self.withdrawQuota = 0  # 可取金额
        self.user_id = self.user_id
        self.password = self.password
        self.money = 0
        self.close_profit = 0
        self.event_id = 0
        self.transfers = {}
        self.banks = {}
        self.event = {}
        self.positions = {}
        self.trades = {}
        self.orders = {}
        self.banks[str(self.bank_id)] = {
            "id": self.bank_id,
            "name": self.bankname,
            "bank_account": "",
            "fetch_amount": 0.0,
            "qry_count": 0
        }
        self.ask_deposit(self.init_cash)

    def create_backtestaccount(self):
        """
        生成一个回测的账户

        回测账户的核心事件轴是数据的datetime, 基于数据的datetime来进行账户的更新


        """
        self._trading_day = ""
        self.pre_balance = self.init_cash
        self.static_balance = self.init_cash
        self.deposit = 0  # 入金
        self.withdraw = 0  # 出金
        self.withdrawQuota = 0  # 可取金额
        self.user_id = self.user_id
        self.password = self.password
        self.money = self.init_cash
        self.close_profit = 0
        self.event_id = 0
        self.transfers = {}
        self.banks = {}
        self.event = {}
        self.positions = {}
        self.trades = {}
        self.orders = {}
        self.banks[str(self.bank_id)] = {
            "id": self.bank_id,
            "name": self.bankname,
            "bank_account": "",
            "fetch_amount": 0.0,
            "qry_count": 0
        }

        # self.ask_deposit(self.init_cash)

    def add_position(self, position):

        if position.instrument_id not in self.positions.keys():
            self.positions[position.instrument_id] = position
            return 0
        else:
            return 1

    def drop_position(self, position):
        pass

    def log(self, message):
        print(message)
        #self.event[self.dtstr] = message

    @property
    def open_orders(self):
        return [
            item for item in self.orders.values() if item['volume_left'] > 0
        ]

    @property
    def message(self):
        return {
            # // 账户号(兼容QUANTAXIS QAAccount)// 实盘的时候是 账户id
            "account_cookie": self.user_id,
            "password": self.password,
            "databaseip": self.trade_host,
            "model": self.model,
            "ping_gap": 5,
            "portfolio": self.portfolio,
            "broker_name": self.broker_name,  # // 接入商名称
            "capital_password": self.capital_password,  # // 资金密码 (实盘用)
            "bank_password": self.bank_password,  # // 银行密码(实盘用)
            "bankid": self.bank_id,  # // 银行id
            "investor_name": self.investor_name,  # // 开户人名称
            "money": self.money,  # // 当前可用现金
            "pub_host": self.pub_host,
            "trade_host": self.trade_host,
            "taskid": self.taskid,
            "sourceid": self.source_id,
            "updatetime": str(self.last_updatetime),
            "wsuri": self.wsuri,
            "bankname": self.bankname,
            "trading_day": str(self.trading_day),
            "status": self.status,
            "accounts": self.account_msg,
            "trades": self.trades,
            "positions": self.position_msg,
            "orders": self.orders,
            "event": self.event,
            "transfers": self.transfers,
            "banks": self.banks,
            "frozen": self.frozen,
            "settlement": {},
        }

    @property
    def account_msg(self):
        return {
            "user_id": self.user_id,
            "currency": "CNY",
            "pre_balance": self.pre_balance,
            "deposit": self.deposit,
            "withdraw": self.withdraw,
            "WithdrawQuota": self.withdrawQuota,
            "close_profit": self.close_profit,
            "commission": self.commission,
            "premium": self.premium,
            "static_balance": self.static_balance,
            "position_profit": self.position_profit,
            "float_profit": self.float_profit,
            "balance": self.balance,
            "margin": self.margin,
            "frozen_margin": self.frozen_margin,
            "frozen_commission": 0.0,
            "frozen_premium": 0.0,
            "available": self.available,
            "risk_ratio": 1 - self.available / self.balance
        }

    @property
    def position_msg(self):
        return dict(
            zip(self.positions.keys(),
                [item.message for item in self.positions.values()]))

    @property
    def position_profit(self):
        return sum(
            [position.position_profit for position in self.positions.values()])

    @property
    def float_profit(self):
        return sum(
            [position.float_profit for position in self.positions.values()])

    @property
    def frozen_margin(self):
        return sum([item.get('money') for item in self.frozen.values()])

    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.replace('_', '.'), '%Y-%m-%d %H:%M:%S.%f')
            return tradedt.timestamp() * 1000000000
        elif isinstance(times, datetime.datetime):
            return tradedt.timestamp() * 1000000000


# 惰性计算

    @property
    def available(self):
        return self.money

    @property
    def margin(self):
        """保证金
        """
        return sum([position.margin for position in self.positions.values()])

    @property
    def commission(self):
        """本交易日内交纳的手续费
        """
        return sum(
            [position.commission for position in self.positions.values()])

    @property
    def balance(self):
        """动态权益

        Arguments:
            self {[type]} -- [description]
        """

        return self.static_balance + self.deposit - self.withdraw + self.float_profit + self.close_profit

    def order_check(self, code: str, amount: int, price: float, towards: int,
                    order_id: str) -> bool:
        """
        order_check是账户自身的逻辑, 你可以重写这个代码

        Attention: 需要注意的是 如果你修改了此部分代码 请注意如果你做了对于账户的资金的预操作请在结束的时候恢复
        
        :::如: 下单失败-> 请恢复账户的资金和仓位

        --> return  Bool
        """
        res = False
        qapos = self.get_position(code)

        self.log(qapos.curpos)
        self.log(qapos.close_available)
        if towards == ORDER_DIRECTION.BUY_CLOSE:
            # self.log("buyclose")
            # self.log(self.volume_short - self.volume_short_frozen)
            # self.log(amount)
            if (qapos.volume_short - qapos.volume_short_frozen) >= amount:
                # check
                qapos.volume_short_frozen_today += amount
                #qapos.volume_short_today -= amount
                res = True
            else:
                self.log("BUYCLOSE 仓位不足")

        elif towards == ORDER_DIRECTION.BUY_CLOSETODAY:
            if (qapos.volume_short_today -
                    qapos.volume_short_frozen_today) >= amount:
                qapos.volume_short_frozen_today += amount
                #qapos.volume_short_today -= amount
                res = True
            else:
                self.log("BUYCLOSETODAY 今日仓位不足")
        elif towards in [ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL]:
            # self.log("sellclose")
            # self.log(self.volume_long - self.volume_long_frozen)
            # self.log(amount)
            if (qapos.volume_long - qapos.volume_long_frozen) >= amount:
                qapos.volume_long_frozen_today += amount
                #qapos.volume_long_today -= amount
                res = True
            else:
                self.log("SELL CLOSE 仓位不足")

        elif towards == ORDER_DIRECTION.SELL_CLOSETODAY:
            if (qapos.volume_long_today -
                    qapos.volume_long_frozen_today) >= amount:
                # self.log("sellclosetoday")
                # self.log(self.volume_long_today - self.volume_long_frozen)
                # self.log(amount)
                qapos.volume_long_frozen_today += amount
                #qapos.volume_long_today -= amount
                return True
            else:
                self.log("SELLCLOSETODAY 今日仓位不足")
        elif towards in [
                ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN,
                ORDER_DIRECTION.BUY
        ]:
            """
            冻结的保证金
            """
            coeff = float(price) * float(
                self.market_preset.get_code(code).get(
                    "unit_table", 1)) * float(
                        self.market_preset.get_code(code).get(
                            "buy_frozen_coeff", 1))
            moneyneed = coeff * amount
            if self.available > moneyneed:
                self.money -= moneyneed
                self.frozen[order_id] = {
                    'amount': amount,
                    'coeff': coeff,
                    'money': moneyneed
                }
                res = True
            else:
                self.log("开仓保证金不足 TOWARDS{} Need{} HAVE{}".format(
                    towards, moneyneed, self.available))

        return res

    def send_order(self,
                   code: str,
                   amount: float,
                   price: float,
                   towards: int,
                   order_id: str = '',
                   datetime: str = ''):
        order_id = str(uuid.uuid4()) if order_id == '' else order_id
        if self.order_check(code, amount, price, towards, order_id):
            self.log("order check success")
            direction, offset = parse_orderdirection(towards)
            self.event_id += 1
            order = {
                "account_cookie": self.user_id,
                "user_id": self.user_id,
                "instrument_id": code,
                "towards": int(towards),
                "exchange_id": self.market_preset.get_exchange(code),
                "order_time": self.dtstr,
                "volume": int(amount),
                "price": float(price),
                "order_id": order_id,
                "seqno": self.event_id,
                "direction": direction,
                "offset": offset,
                "volume_orign": int(amount),
                "price_type": "LIMIT",
                "limit_price": float(price),
                "time_condition": "GFD",
                "volume_condition": "ANY",
                "insert_date_time": self.transform_dt(self.dtstr),
                'order_time': self.dtstr,
                "exchange_order_id": str(uuid.uuid4()),
                "status": "ALIVE",
                "volume_left": int(amount),
                "last_msg": "已报"
            }
            self.orders[order_id] = order
            self.log('下单成功 {}'.format(order_id))
            self.sync()
            self.on_ordersend(order)
            return order
        else:
            self.log(RuntimeError("ORDER CHECK FALSE: {}".format(code)))
            return False

    def on_ordersend(self, order):
        pass

    def cancel_order(self, order_id):
        """Initial
        撤单/ 释放冻结/

        """
        od = self.orders[order_id]
        od['last_msg'] = '已撤单'
        od['status'] = "CANCEL"
        od['volume_left'] = 0

        if od['offset'] in ['CLOSE', 'CLOSETODAY']:
            pos = self.positions[od['instrument_id']]
            if od['direction'] == 'BUY':
                pos.volume_short_frozen_today += od['volume_left']
            else:
                pos.volume_long_frozen_today += od['volume_left']
        else:
            frozen = self.frozen[order_id]
            self.money += frozen['money']
            frozen['amount'] = 0
            frozen['money'] = 0
            self.frozen[order_id] = frozen

        self.orders[order_id] = od

        self.log('撤单成功 {}'.format(order_id))

    def make_deal(self, order: dict):
        if isinstance(order, dict):
            self.receive_deal(order["instrument_id"],
                              trade_price=order["limit_price"],
                              trade_time=self.dtstr,
                              trade_amount=order["volume_left"],
                              trade_towards=order["towards"],
                              order_id=order['order_id'],
                              trade_id=str(uuid.uuid4()))

    def receive_deal(self,
                     code,
                     trade_price,
                     trade_amount,
                     trade_towards,
                     trade_time,
                     message=None,
                     order_id=None,
                     trade_id=None,
                     realorder_id=None):
        if order_id in self.orders.keys():

            # update order
            od = self.orders[order_id]
            frozen = self.frozen.get(order_id, {
                'order_id': order_id,
                'money': 0,
                'price': 0
            })
            vl = od.get('volume_left', 0)
            if trade_amount == vl:

                self.money += frozen['money']
                frozen['amount'] = 0
                frozen['money'] = 0
                od['last_msg'] = '全部成交'
                od["status"] = "FINISHED"
                self.log('全部成交 {}'.format(order_id))

            elif trade_amount < vl:
                frozen['amount'] = vl - trade_amount
                release_money = trade_amount * frozen.get('coeff', 1)
                self.money += release_money

                frozen['money'] -= release_money

                od['last_msg'] = '部分成交'
                od["status"] = "ALIVE"
                self.log('部分成交 {}'.format(order_id))

            od['volume_left'] -= trade_amount

            self.orders[order_id] = od
            self.frozen[order_id] = frozen
            # update trade
            self.event_id += 1
            trade_id = str(uuid.uuid4()) if trade_id is None else trade_id

            self.trades[trade_id] = {
                "seqno": self.event_id,
                "user_id": self.user_id,
                "trade_id": trade_id,
                "exchange_id": od['exchange_id'],
                "instrument_id": od['instrument_id'],
                "order_id": order_id,
                "exchange_trade_id": trade_id,
                "direction": od['direction'],
                "offset": od['offset'],
                "volume": trade_amount,
                "price": trade_price,
                "trade_time": trade_time,
                "commission": float(0),
                "trade_date_time": self.transform_dt(trade_time)
            }

            # update accounts
            print('update trade')

            margin, close_profit = self.get_position(code).update_pos(
                trade_price, trade_amount, trade_towards)

            self.money -= (margin - close_profit)
            self.close_profit += close_profit

            self.sync()

    def get_position(self, code: str = None) -> QA_Position:
        if code is None:
            return list(self.positions.values())[0]
        else:
            if code not in self.positions.keys():
                self.positions[code] = QA_Position(code=code)
            return self.positions[code]

    def query_trade(self):
        pass

    def on_tick(self, tick):
        pass

    def on_bar(self, bar):
        pass

    def on_price_change(self, code, price, datetime=None):

        if code in self.positions.keys():
            try:
                pos = self.get_position(code)
                if pos.last_price == price:
                    pass
                else:
                    pos.last_price = price
                    self.sync()
            except Exception as e:

                self.log(e)

        if datetime:
            self.datetime = datetime

    def order_schedule(self,
                       code: str,
                       amount: float,
                       price: float,
                       towards: int,
                       order_id: str = ''):
        """
        预调仓接口
        """
        if order_id == '':
            order_id = str(uuid.uuid4())
        orderx = {
            'code': code,
            'amount': amount,
            'price': price,
            'towards': towards,
            'order_id': order_id
        }
        self.schedule[order_id] = orderx
Example #2
0
class QA_Account(QA_Worker):
    """QA_Account
    User-->Portfolio-->Account/Strategy

    :::::::::::::::::::::::::::::::::::::::::::::::::
    ::        :: Portfolio 1 -- Account/Strategy 1 ::
    ::  USER  ::             -- Account/Strategy 2 ::
    ::        :: Portfolio 2 -- Account/Strategy 3 ::
    :::::::::::::::::::::::::::::::::::::::::::::::::

    2018/1/5 再次修改 改版本去掉了多余的计算 精简账户更新
    ======================

    - 不再计算总资产/不再计算当前持仓/不再计算交易对照明细表
    - 不再动态计算账户股票/期货市值
    - 只维护 cash/history两个字段 剩下的全部惰性计算


    QA_Account 是QUANTAXIS的最小不可分割单元之一

    QA_Account是账户类 需要兼容股票/期货/指数
    QA_Account继承自QA_Worker 可以被事件驱动
    QA_Account可以直接被QA_Strategy继承

    有三类输入:
    信息类: 账户绑定的策略名/账户的用户名/账户类别/账户识别码/账户的broker
    资产类: 现金/可用现金/交易历史/交易对照表
    规则类: 是否允许卖空/是否允许t0结算

    方法:
    惰性计算:最新持仓/最新总资产/最新现金/持仓面板
    生成订单/接受交易结果数据
    接收新的数据/on_bar/on_tick方法/缓存新数据的market_data

    @royburns  1.添加注释
    2018/05/18

    T0交易的sell_available和正常的sell_available不一样:

    T0交易中, 当买入一笔/卖出一笔, 当天操作额度都会下降

    T0的订单-账户对应系统


    @2018/06/11
    QA_Account不会基于行情计算市值,因此都只会对应记录证券数量和现金资产




    @2018/12/23
    当我们继承/复用 QA_Account 的时候, 我们需要实现什么

    - init_cash
    - init_hold
    - broker



    @2018/12/24
    账户需要追踪

    @2018/12/31
    账户需要截面数据 
    需要在任意截面的基础上往下做

    基础的信息截面 ==>  init_cash/init_hold |  history的初始值

    任意时间点的信息截面

    """
    def __init__(self,
                 strategy_name=None,
                 user_cookie=None,
                 portfolio_cookie=None,
                 account_cookie=None,
                 market_type=MARKET_TYPE.STOCK_CN,
                 frequence=FREQUENCE.DAY,
                 broker=BROKER_TYPE.BACKETEST,
                 init_hold={},
                 init_cash=1000000,
                 commission_coeff=0.00025,
                 tax_coeff=0.001,
                 margin_level={},
                 allow_t0=False,
                 allow_sellopen=False,
                 allow_margin=False,
                 running_environment=RUNNING_ENVIRONMENT.BACKETEST):
        """

        :param [str] strategy_name:  策略名称
        :param [str] user_cookie:   用户cookie
        :param [str] portfolio_cookie: 组合cookie
        :param [str] account_cookie:   账户cookie

        :param [dict] init_hold         初始化时的股票资产
        :param [float] init_cash:         初始化资金
        :param [float] commission_coeff:  交易佣金 :默认 万2.5   float 类型
        :param [float] tax_coeff:         印花税   :默认 千1.5   float 类型

        :param [Bool] margin_level:      保证金比例 默认{}
        :param [Bool] allow_t0:          是否允许t+0交易  默认False
        :param [Bool] allow_sellopen:    是否允许卖空开仓  默认False
        :param [Bool] allow_margin:      是否允许保证金交易 默认False

        ### 注意
        >>>>>>>>>>>>>
        在期货账户中:
        allow_t0/ allow_sellopen 是必须打开的

        allow_margin 是作为保证金账户的开关 默认关闭 可以打开 则按照market_preset中的保证金比例来计算
        具体可以参见: https://github.com/QUANTAXIS/QUANTAXIS/blob/master/EXAMPLE/test_backtest/FUTURE/TEST_%E4%BF%9D%E8%AF%81%E9%87%91%E8%B4%A6%E6%88%B7.ipynb

        >>>>>>>>>>>>>



        :param [QA.PARAM] market_type:   市场类别 默认QA.MARKET_TYPE.STOCK_CN A股股票
        :param [QA.PARAM] frequence:     账户级别 默认日线QA.FREQUENCE.DAY
        :param [QA.PARAM] broker:        BROEKR类 默认回测 QA.BROKER_TYPE.BACKTEST
        :param [QA.PARAM] running_environment 当前运行环境 默认Backtest

        # 2018/06/11 init_assets 从float变为dict,并且不作为输入,作为只读属性
        #  :param [float] init_assets:       初始资产  默认 1000000 元 (100万)
        init_assets:{
            cash: xxx,
            stock: {'000001':2000},
            init_date: '2018-02-05',
            init_datetime: '2018-02-05 15:00:00'
        }
        # 2018/06/11 取消在初始化的时候的cash和history输入
        # :param [list] cash:              可用现金  默认 是 初始资产  list 类型
        # :param [list] history:           交易历史


        # 2018/11/9 修改保证金交易

        # 我们把冻结的保证金 看做是未来的已实现交易:
        # 如==> 当前的一手空单 认为是未来的卖出成交(已知价格 不知时间)
        # 因此我们如此对于保证金交易进行评估:
        # 账户买入:
        多单开仓:  cash 下降x 保证金增加x 增加一手未来的卖出合约(持仓)  ==> 平仓: cash上升 保证金恢复
        cash + frozen(平仓释放) + 未平仓位

        cash, available_cash

        frozen{
                RB1901: {
                        towards 2: {avg_money : xxx, amount: xxx, queue: collection.deque()},
                        towards -2: {avg_money, amount, queue: collection.deque()}
                        },
                IF1901: {
                        towards 2: {avg_money, amount,queue: collection.deque()},
                        towards -2: {avg_money, amount,queue: collection.deque()}
                }
            }
        }

        hold: {
            RB1901: {
                1, amount, # 多单待平仓
                -1, amount # 空单待平仓
            }
        }
        """
        super().__init__()
        # warnings.warn('QUANTAXIS 1.0.46 has changed the init_assets ==> init_cash, please pay attention to this change if you using init_cash to initial an account class,\
        #         ', DeprecationWarning, stacklevel=2)
        self._history_headers = [
            'datetime', 'code', 'price', 'amount', 'cash', 'order_id',
            'realorder_id', 'trade_id', 'account_cookie', 'commission', 'tax',
            'message', 'frozen'
        ]
        ########################################################################
        # 信息类:
        self.strategy_name = strategy_name
        self.user_cookie = user_cookie
        self.portfolio_cookie = portfolio_cookie
        self.account_cookie = QA_util_random_with_topic(
            'Acc') if account_cookie is None else account_cookie

        self.market_type = market_type
        self.broker = broker
        self.frequence = frequence
        self.running_environment = running_environment
        ########################################################################
        self._market_data = None
        self._currenttime = None
        self.commission_coeff = commission_coeff
        self.tax_coeff = tax_coeff
        self.datetime = None
        self.running_time = datetime.datetime.now()
        self.quantaxis_version = __version__
        ########################################################################
        # 资产类
        self.orders = QA_OrderQueue()  # 历史委托单
        self.init_cash = init_cash
        self.init_hold = pd.Series(init_hold, name='amount') if isinstance(
            init_hold, dict) else init_hold
        self.init_hold.index.name = 'code'
        self.cash = [self.init_cash]
        self.cash_available = self.cash[-1]  # 可用资金
        self.sell_available = copy.deepcopy(self.init_hold)
        self.buy_available = copy.deepcopy(self.init_hold)
        self.history = []
        self.time_index = []

        # 在回测中, 每日结算后更新
        # 真实交易中, 为每日初始化/每次重新登录后的同步信息
        self.static_balance = {
            'static_assets': [],
            'cash': [],
            'frozen': [],
            'hold': [],
            'date': []
        }  # 日结算
        self.today_trade = {'last': [], 'current': []}
        self.today_orders = {'last': [], 'current': []}

        ########################################################################
        # 规则类
        # 1.是否允许t+0 及买入及结算
        # 2.是否允许卖空开仓
        # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式)

        # 期货: allow_t0 True allow_sellopen True
        #

        self.allow_t0 = allow_t0
        self.allow_sellopen = allow_sellopen
        self.allow_margin = allow_margin
        self.margin_level = margin_level  # 保证金比例

        if self.market_type is MARKET_TYPE.FUTURE_CN:
            self.allow_t0 = True
            self.allow_sellopen = True

        if self.allow_t0 and self.allow_sellopen or self.market_type is MARKET_TYPE.FUTURE_CN:
            self.load_marketpreset()
        """期货的多开/空开 ==> 资金冻结进保证金  frozen

        对应平仓的时候, 释放保证金

        1. frozen  是一个dict :   {[code]:queue}
            key是标的 value是对应的交易queue

        """

        self.frozen = {}  # 冻结资金(保证金)

    def __repr__(self):
        return '< QA_Account {}>'.format(self.account_cookie)

    @property
    def message(self):
        'the standard message which can be transfer'
        return {
            'source':
            'account',
            'account_cookie':
            self.account_cookie,
            'portfolio_cookie':
            self.portfolio_cookie,
            'user_cookie':
            self.user_cookie,
            'broker':
            self.broker,
            'market_type':
            self.market_type,
            'strategy_name':
            self.strategy_name,
            'current_time':
            str(self._currenttime),
            'allow_sellopen':
            self.allow_sellopen,
            'allow_t0':
            self.allow_t0,
            'margin_level':
            self.margin_level,
            'init_assets':
            self.init_assets,
            'init_cash':
            self.init_cash,
            'init_hold':
            self.init_hold.to_dict(),
            'commission_coeff':
            self.commission_coeff,
            'tax_coeff':
            self.tax_coeff,
            'cash':
            self.cash,
            'history':
            self.history,
            'trade_index':
            self.time_index,
            'running_time':
            str(datetime.datetime.now())
            if self.running_time is None else str(self.running_time),
            'quantaxis_version':
            self.quantaxis_version,
            'running_environment':
            self.running_environment,
            'start_date':
            self.start_date,
            'end_date':
            self.end_date
        }

    def load_marketpreset(self):
        """加载市场表
        """

        self.market_preset = MARKET_PRESET()

    @property
    def init_hold_with_account(self):
        """带account_id的初始化持仓

        Returns:
            [type] -- [description]
        """

        return self.init_hold.reset_index().assign(
            account_cookie=self.account_cookie).set_index(
                ['code', 'account_cookie'])

    @property
    def init_assets(self):
        """初始化账户资产

        Returns:
            dict -- 2keys-cash,hold
        """

        return {'cash': self.init_cash, 'hold': self.init_hold.to_dict()}

    @property
    def code(self):
        """
        该账户曾交易代码 用set 去重
        """
        return list(set([item[1] for item in self.history]))

    @property
    def date(self):
        """账户运行的日期

        Arguments:
            self {[type]} -- [description]

        Returns:
            [type] -- [description]
        """

        if self.datetime is not None:
            return str(self.datetime)[0:10]
        else:
            return None

    @property
    def poisitions(self):
        raise NotImplementedError

    @property
    def start_date(self):
        """账户的起始交易日期(只在回测中使用)

        Raises:
            RuntimeWarning -- [description]

        Returns:
            [type] -- [description]
        """

        if len(self.time_index) > 0:
            return str(min(self.time_index))[0:10]
        else:
            print(
                RuntimeWarning(
                    'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE'))

    @property
    def end_date(self):
        """账户的交易结束日期(只在回测中使用)

        Raises:
            RuntimeWarning -- [description]

        Returns:
            [type] -- [description]
        """

        if len(self.time_index) > 0:
            return str(max(self.time_index))[0:10]
        else:
            print(
                RuntimeWarning(
                    'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE'))

    @property
    def market_data(self):
        return self._market_data

    @property
    def trade_range(self):
        return QA_util_get_trade_range(self.start_date, self.end_date)

    @property
    def trade_day(self):
        return list(
            pd.Series(self.time_index).apply(lambda x: str(x)[0:10]).unique())

    @property
    def history_table(self):
        '交易历史的table'
        if len(self.history) > 0:
            lens = len(self.history[0])
        else:
            lens = len(self._history_headers)

        return pd.DataFrame(data=self.history,
                            columns=self._history_headers[:lens]).sort_index()

    @property
    def today_trade_table(self):
        return pd.DataFrame(data=self.today_trade['current'],
                            columns=self._history_headers).sort_index()

    @property
    def cash_table(self):
        '现金的table'
        _cash = pd.DataFrame(data=[self.cash[1::], self.time_index],
                             index=['cash', 'datetime']).T
        _cash = _cash.assign(date=_cash.datetime.apply(
            lambda x: pd.to_datetime(str(x)[0:10]))).assign(
                account_cookie=self.account_cookie)  # .sort_values('datetime')
        return _cash.set_index(['datetime', 'account_cookie'], drop=False)
        """
        实验性质
        @2018-06-09

        # 对于账户持仓的分解

        1. 真实持仓hold:

        正常模式/TZero模式:
            hold = 历史持仓(init_hold)+ 初始化账户后发生的所有交易导致的持仓(hold_available)

        动态持仓(初始化账户后的持仓)hold_available:
            self.history 计算而得

        2. 账户的可卖额度(sell_available)

        正常模式:
            sell_available
                结算前: init_hold+ 买卖交易(卖-)
                结算后: init_hold+ 买卖交易(买+ 卖-)
        TZero模式:
            sell_available
                结算前: init_hold - 买卖交易占用的额度(abs(买+ 卖-))
                结算过程 是为了补平(等于让hold={})
                结算后: init_hold
        """

    @property
    def hold(self):
        """真实持仓
        """
        return pd.concat([self.init_hold,
                          self.hold_available]).groupby('code').sum().replace(
                              0, np.nan).dropna().sort_index()

    @property
    def hold_available_temp(self):
        """可用持仓
        """
        return self._table.groupby('code').amount.sum().replace(
            0, np.nan).dropna().sort_index()

    @property
    def hold_available(self):
        """可用持仓
        """
        return self.history_table.groupby('code').amount.sum().replace(
            0, np.nan).dropna().sort_index()

    # @property
    # def order_table(self):
    #     """return order trade list"""
    #     return self.orders.trade_list

    @property
    def trade(self):
        """每次交易的pivot表

        Returns:
            pd.DataFrame

            此处的pivot_table一定要用np.sum
        """

        return self.history_table.pivot_table(
            index=['datetime', 'account_cookie'],
            columns='code',
            values='amount',
            aggfunc=np.sum).fillna(0).sort_index()

    @property
    def daily_cash(self):
        '每日交易结算时的现金表'
        res = self.cash_table.drop_duplicates(subset='date', keep='last')

        return pd.concat([res.set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\
            .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie'], drop=False).sort_index()

    @property
    def daily_hold(self):
        '每日交易结算时的持仓表'
        data = self.trade.cumsum()
        if len(data) < 1:
            return None
        else:
            # print(data.index.levels[0])
            data = data.assign(account_cookie=self.account_cookie).assign(
                date=pd.to_datetime(data.index.levels[0]).date)

            data.date = pd.to_datetime(data.date)
            data = data.set_index(['date', 'account_cookie'])
            res = data[~data.index.duplicated(keep='last')].sort_index()
            # 这里会导致股票停牌时的持仓也被计算 但是计算market_value的时候就没了
            return pd.concat([res.reset_index().set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\
                .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie']).sort_index()

    # 计算assets的时候 需要一个market_data=QA.QA_fetch_stock_day_adv(list(data.columns),data.index[0],data.index[-1])
    # (market_data.to_qfq().pivot('close')*data).sum(axis=1)+user_cookie.get_account(a_1).daily_cash.set_index('date').cash

    @property
    def latest_cash(self):
        'return the lastest cash 可用资金'
        return self.cash[-1]

    @property
    def current_time(self):
        'return current time (in backtest/real environment)'
        return self._currenttime

    def hold_table(self, datetime=None):
        "到某一个时刻的持仓 如果给的是日期,则返回当日开盘前的持仓"
        if datetime is None:
            hold_available = self.history_table.set_index(
                'datetime').sort_index().groupby(
                    'code').amount.sum().sort_index()
        else:
            hold_available = self.history_table.set_index(
                'datetime').sort_index().loc[:datetime].groupby(
                    'code').amount.sum().sort_index()

        return pd.concat([self.init_hold, hold_available
                          ]).groupby('code').sum().sort_index().apply(
                              lambda x: x if x > 0 else None).dropna()

    def hold_price(self, datetime=None):
        """计算持仓成本  如果给的是日期,则返回当日开盘前的持仓

        Keyword Arguments:
            datetime {[type]} -- [description] (default: {None})

        Returns:
            [type] -- [description]
        """
        def weights(x):
            if sum(x['amount']) != 0:
                return np.average(x['price'],
                                  weights=x['amount'],
                                  returned=True)
            else:
                return np.nan

        if datetime is None:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().groupby('code').apply(
                    weights).dropna()
        else:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().loc[:datetime].groupby(
                    'code').apply(weights).dropna()

    # @property
    def hold_time(self, datetime=None):
        """持仓时间

        Keyword Arguments:
            datetime {[type]} -- [description] (default: {None})
        """
        def weights(x):
            if sum(x['amount']) != 0:
                return pd.Timestamp(self.datetime) - pd.to_datetime(
                    x.datetime.max())
            else:
                return np.nan

        if datetime is None:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().groupby('code').apply(
                    weights).dropna()
        else:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().loc[:datetime].groupby(
                    'code').apply(weights).dropna()

    def reset_assets(self, init_cash=None):
        'reset_history/cash/'
        self.sell_available = copy.deepcopy(self.init_hold)
        self.history = []
        self.init_cash = init_cash
        self.cash = [self.init_cash]
        self.cash_available = self.cash[-1]  # 在途资金

    def receive_simpledeal(self,
                           code,
                           trade_price,
                           trade_amount,
                           trade_towards,
                           trade_time,
                           message=None,
                           order_id=None,
                           trade_id=None,
                           realorder_id=None):
        """快速撮合成交接口


        此接口是一个直接可以成交的接口, 所以务必确保给出的信息是可以成交的

        此接口涉及的是
        1. 股票/期货的成交
        2. 历史记录的增加
        3. 现金/持仓/冻结资金的处理

        Arguments:
            code {[type]} -- [description]
            trade_price {[type]} -- [description]
            trade_amount {[type]} -- [description]
            trade_towards {[type]} -- [description]
            trade_time {[type]} -- [description]

        Keyword Arguments:
            message {[type]} -- [description] (default: {None})


        2018/11/7 @yutiansut
        修复一个bug: 在直接使用该快速撮合接口的时候, 期货卖出会扣减保证金, 买回来的时候应该反算利润

        如 3800卖空 3700买回平仓  应为100利润
        @2018-12-31 保证金账户ok


        @2019/1/3 一些重要的意思
        frozen = self.market_preset.get_frozen(code) # 保证金率
        unit = self.market_preset.get_unit(code)  # 合约乘数
        raw_trade_money = trade_price*trade_amount*market_towards  # 总市值
        value = raw_trade_money * unit  # 合约总价值
        trade_money = value * frozen    # 交易保证金
        """

        self.datetime = trade_time

        market_towards = 1 if trade_towards > 0 else -1
        # value 合约价值 unit 合约乘数
        if self.allow_margin:
            frozen = self.market_preset.get_frozen(code)  # 保证金率
            unit = self.market_preset.get_unit(code)  # 合约乘数
            raw_trade_money = trade_price * trade_amount * market_towards  # 总市值
            value = raw_trade_money * unit  # 合约总价值
            trade_money = value * frozen  # 交易保证金
        else:
            trade_money = trade_price * trade_amount * market_towards
            raw_trade_money = trade_money
            value = trade_money
            unit = 1
            frozen = 1
            # 计算费用
            # trade_price

        if self.market_type == MARKET_TYPE.FUTURE_CN:
            # 期货不收税
            # 双边手续费 也没有最小手续费限制

            commission_fee_preset = self.market_preset.get_code(code)
            if trade_towards in [
                    ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE,
                    ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN
            ]:
                commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_peramount'] * \
                    abs(value)
            elif trade_towards in [
                    ORDER_DIRECTION.BUY_CLOSETODAY,
                    ORDER_DIRECTION.SELL_CLOSETODAY
            ]:
                commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_today_peramount'] * \
                    abs(value)

            tax_fee = 0  # 买入不收印花税
        elif self.market_type == MARKET_TYPE.STOCK_CN:

            commission_fee = self.commission_coeff * \
                abs(trade_money)

            commission_fee = 5 if commission_fee < 5 else commission_fee
            if int(trade_towards) > 0:
                tax_fee = 0  # 买入不收印花税
            else:
                tax_fee = self.tax_coeff * abs(trade_money)

        # 结算交易
        if self.cash[-1] > trade_money + commission_fee + tax_fee:
            self.time_index.append(trade_time)
            # TODO: 目前还不支持期货的锁仓
            if self.allow_sellopen:
                if trade_towards in [
                        ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN
                ]:
                    # 开仓单占用现金 计算avg
                    # 初始化
                    if code in self.frozen.keys():
                        if trade_towards in self.frozen[code].keys():
                            pass
                        else:
                            self.frozen[code][trade_towards] = {
                                'money': 0,
                                'amount': 0,
                                'avg_price': 0
                            }
                    else:
                        self.frozen[code] = {
                            ORDER_DIRECTION.BUY_OPEN: {
                                'money': 0,
                                'amount': 0,
                                'avg_price': 0
                            },
                            ORDER_DIRECTION.SELL_OPEN: {
                                'money': 0,
                                'amount': 0,
                                'avg_price': 0
                            }
                        }
                    """[summary]
                    # frozen的计算
                    # money 冻结的资金
                    # amount  冻结的数量

                    2018-12-31                    

                    """

                    self.frozen[code][trade_towards]['money'] = (
                        (self.frozen[code][trade_towards]['money'] *
                         self.frozen[code][trade_towards]['amount']) +
                        abs(trade_money)) / (
                            self.frozen[code][trade_towards]['amount'] +
                            trade_amount)
                    self.frozen[code][trade_towards]['avg_price'] = (
                        (self.frozen[code][trade_towards]['avg_price'] *
                         self.frozen[code][trade_towards]['amount']) +
                        abs(raw_trade_money)) / (
                            self.frozen[code][trade_towards]['amount'] +
                            trade_amount)
                    self.frozen[code][trade_towards]['amount'] += trade_amount

                    self.cash.append(self.cash[-1] - abs(trade_money) -
                                     commission_fee - tax_fee)
                elif trade_towards in [
                        ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE
                ]:
                    # 平仓单释放现金
                    # if trade_towards == ORDER_DIRECTION.BUY_CLOSE:
                    # 卖空开仓 平仓买入
                    # self.cash
                    if trade_towards == ORDER_DIRECTION.BUY_CLOSE:  # 买入平仓  之前是空开
                        # self.frozen[code][ORDER_DIRECTION.SELL_OPEN]['money'] -= trade_money
                        self.frozen[code][ORDER_DIRECTION.
                                          SELL_OPEN]['amount'] -= trade_amount

                        frozen_part = self.frozen[code][
                            ORDER_DIRECTION.SELL_OPEN]['money'] * trade_amount
                        # 账户的现金+ 冻结的的释放 + 买卖价差* 杠杆
                        self.cash.append(self.cash[-1] + frozen_part +
                                         (frozen_part - trade_money) / frozen -
                                         commission_fee - tax_fee)
                        if self.frozen[code][
                                ORDER_DIRECTION.SELL_OPEN]['amount'] == 0:
                            self.frozen[code][
                                ORDER_DIRECTION.SELL_OPEN]['money'] = 0
                            self.frozen[code][
                                ORDER_DIRECTION.SELL_OPEN]['avg_price'] = 0

                    elif trade_towards == ORDER_DIRECTION.SELL_CLOSE:  # 卖出平仓  之前是多开
                        # self.frozen[code][ORDER_DIRECTION.BUY_OPEN]['money'] -= trade_money
                        self.frozen[code][
                            ORDER_DIRECTION.BUY_OPEN]['amount'] -= trade_amount

                        frozen_part = self.frozen[code][
                            ORDER_DIRECTION.BUY_OPEN]['money'] * trade_amount
                        self.cash.append(self.cash[-1] + frozen_part +
                                         (abs(trade_money) - frozen_part) /
                                         frozen - commission_fee - tax_fee)
                        if self.frozen[code][
                                ORDER_DIRECTION.BUY_OPEN]['amount'] == 0:
                            self.frozen[code][
                                ORDER_DIRECTION.BUY_OPEN]['money'] = 0
                            self.frozen[code][
                                ORDER_DIRECTION.BUY_OPEN]['avg_price'] = 0
            else:  # 不允许卖空开仓的==> 股票

                self.cash.append(self.cash[-1] - trade_money - tax_fee -
                                 commission_fee)
            if self.allow_t0 or trade_towards == ORDER_DIRECTION.SELL:
                self.sell_available[code] = self.sell_available.get(
                    code, 0) + trade_amount * market_towards
                self.buy_available = self.sell_available

            self.cash_available = self.cash[-1]
            frozen_money = abs(trade_money) if trade_towards in [
                ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN
            ] else 0
            self.history.append([
                trade_time, code, trade_price, market_towards * trade_amount,
                self.cash[-1], order_id, realorder_id, trade_id,
                self.account_cookie, commission_fee, tax_fee, message,
                frozen_money
            ])

        else:
            # print(self.cash[-1])
            self.cash_available = self.cash[-1]
            #print('NOT ENOUGH MONEY FOR {}'.format(order_id))

    def receive_deal(self,
                     code: str,
                     trade_id: str,
                     order_id: str,
                     realorder_id: str,
                     trade_price: float,
                     trade_amount: int,
                     trade_towards: int,
                     trade_time: str,
                     message=None):
        """更新deal

        Arguments:
            code {str} -- [description]
            trade_id {str} -- [description]
            order_id {str} -- [description]
            realorder_id {str} -- [description]
            trade_price {float} -- [description]
            trade_amount {int} -- [description]
            trade_towards {int} -- [description]
            trade_time {str} -- [description]

        Returns:
            [type] -- [description]
        """

        print('receive deal')

        trade_time = str(trade_time)
        code = str(code)
        trade_price = float(trade_price)
        trade_towards = int(trade_towards)
        realorder_id = str(realorder_id)
        trade_id = str(trade_id)
        trade_amount = int(trade_amount)
        order_id = str(order_id)

        market_towards = 1 if trade_towards > 0 else -1
        """2019/01/03 直接使用快速撮合接口了
        2333 这两个接口现在也没啥区别了....
        太绝望了
        """

        self.receive_simpledeal(code,
                                trade_price,
                                trade_amount,
                                trade_towards,
                                trade_time,
                                message=message,
                                order_id=order_id,
                                trade_id=trade_id,
                                realorder_id=realorder_id)

    def send_order(self,
                   code=None,
                   amount=None,
                   time=None,
                   towards=None,
                   price=None,
                   money=None,
                   order_model=None,
                   amount_model=None,
                   *args,
                   **kwargs):
        """
        ATTENTION CHANGELOG 1.0.28
        修改了Account的send_order方法, 区分按数量下单和按金额下单两种方式

        - AMOUNT_MODEL.BY_PRICE ==> AMOUNT_MODEL.BY_MONEY # 按金额下单
        - AMOUNT_MODEL.BY_AMOUNT # 按数量下单

        在按金额下单的时候,应给予 money参数
        在按数量下单的时候,应给予 amount参数

        python code:
        Account=QA.QA_Account()

        Order_bymoney=Account.send_order(code='000001',
                                        price=11,
                                        money=0.3*Account.cash_available,
                                        time='2018-05-09',
                                        towards=QA.ORDER_DIRECTION.BUY,
                                        order_model=QA.ORDER_MODEL.MARKET,
                                        amount_model=QA.AMOUNT_MODEL.BY_MONEY
                                        )

        Order_byamount=Account.send_order(code='000001',
                                        price=11,
                                        amount=100,
                                        time='2018-05-09',
                                        towards=QA.ORDER_DIRECTION.BUY,
                                        order_model=QA.ORDER_MODEL.MARKET,
                                        amount_model=QA.AMOUNT_MODEL.BY_AMOUNT
                                        )

        :param code: 证券代码
        :param amount: 买卖 数量多数股
        :param time:  Timestamp 对象 下单时间
        :param towards: int , towards>0 买入 towards<0 卖出
        :param price: 买入,卖出 标的证券的价格
        :param money: 买卖 价格
        :param order_model: 类型 QA.ORDER_MODE
        :param amount_model:类型 QA.AMOUNT_MODEL
        :return:  QA_Order | False

        @2018/12/23
        send_order 是QA的标准返回, 如需对接其他接口, 只需要对于QA_Order做适配即可


        @2018/12/27
        在判断账户为期货账户(及 允许双向交易)

        @2018/12/30 保证金账户的修改
        1. 保证金账户冻结的金额
        2. 保证金账户的结算
        3. 保证金账户的判断

        """
        wrong_reason = None
        assert code is not None and time is not None and towards is not None and order_model is not None and amount_model is not None

        # 🛠todo 移到Utils类中,  时间转换
        # date 字符串 2011-10-11 长度10
        date = str(time)[0:10] if len(str(time)) == 19 else str(time)
        # time 字符串 20011-10-11 09:02:00  长度 19
        time = str(time) if len(str(time)) == 19 else '{} 09:31:00'.format(
            str(time)[0:10])

        # 🛠todo 移到Utils类中,  amount_to_money 成交量转金额
        # BY_MONEY :: amount --钱 如10000元  因此 by_money里面 需要指定价格,来计算实际的股票数
        # by_amount :: amount --股数 如10000股

        if self.allow_margin:
            amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int(
                money / (self.market_preset.get_unit(code) *
                         self.market_preset.get_frozen(code) * price *
                         (1 + self.commission_coeff)) / 100) * 100
        else:

            amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int(
                money / (price * (1 + self.commission_coeff)) / 100) * 100

        # 🛠todo 移到Utils类中,  money_to_amount 金额转成交量
        if self.allow_margin:
            money = amount * price * self.market_preset.get_unit(code)*self.market_preset.get_frozen(code) * \
                (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money
        else:
            money = amount * price * \
                (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money

        # flag 判断买卖 数量和价格以及买卖方向是否正确
        flag = False

        assert (int(towards) != 0)
        if int(towards) in [1, 2, 3]:
            # 是买入的情况(包括买入.买开.买平)
            if self.cash_available >= money:
                if self.market_type is MARKET_TYPE.STOCK_CN:  # 如果是股票 买入的时候有100股的最小限制
                    amount = int(amount / 100) * 100
                    self.cash_available -= money
                    flag = True

                if self.running_environment == RUNNING_ENVIRONMENT.TZERO:

                    if self.buy_available.get(code, 0) >= amount:
                        flag = True
                        self.cash_available -= money
                        self.buy_available[code] -= amount
                    else:
                        flag = False
                        wrong_reason = 'T0交易买入超出限额'

                if self.market_type == MARKET_TYPE.FUTURE_CN:
                    # 如果有负持仓-- 允许卖空的时候
                    if towards == 3:  # 多平
                        _hold = self.sell_available.get(code, 0)
                        # 假设有负持仓:
                        # amount为下单数量 如  账户原先-3手 现在平1手

                        #left_amount = amount+_hold if _hold < 0 else amount
                        _money = abs(
                            float(amount * price *
                                  (1 + self.commission_coeff)))

                        print(_hold)
                        if self.cash_available >= _money:
                            if _hold < 0:
                                self.cash_available -= _money

                                flag = True
                            else:
                                wrong_reason = '空单仓位不足'
                        else:
                            wrong_reason = '平多剩余资金不够'
                    if towards == 2:
                        self.cash_available -= money
                        flag = True
            else:
                wrong_reason = 'QAACCOUNT: 可用资金不足 cash_available {}  code {} time {} amount {} towards {}'.format(
                    self.cash_available, code, time, amount, towards)
        elif int(towards) in [-1, -2, -3]:
            # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓)
            # print(self.sell_available[code])
            _hold = self.sell_available.get(code, 0)  # _hold 是你的持仓

            # 如果你的hold> amount>0
            # 持仓数量>卖出数量
            if _hold >= amount:
                self.sell_available[code] -= amount
                # towards = ORDER_DIRECTION.SELL
                flag = True
            # 如果持仓数量<卖出数量
            else:

                # 如果是允许卖空开仓 实际计算时  先减去持仓(正持仓) 再计算 负持仓 就按原先的占用金额计算
                if self.allow_sellopen and towards == -2:

                    if self.cash_available >= money:  # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空
                        # self.cash_available -= money
                        flag = True
                    else:
                        print('sellavailable', _hold)
                        print('amount', amount)
                        print('aqureMoney', money)
                        print('cash', self.cash_available)
                        wrong_reason = "卖空资金不足/不允许裸卖空"
                else:
                    wrong_reason = "卖出仓位不足"

        if flag and (amount > 0):
            _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
            # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中
            self.datetime = time
            self.orders.insert_order(_order)
            return _order
        else:
            print('ERROR : CODE {} TIME {}  AMOUNT {} TOWARDS {}'.format(
                code, time, amount, towards))
            print(wrong_reason)
            return False

    def cancel_order(self, order):
        if order.towards in [
                ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN,
                ORDER_DIRECTION.BUY_CLOSE
        ]:
            if order.amount_model is AMOUNT_MODEL.BY_MONEY:
                self.cash_available += order.money
            elif order.amount_model is AMOUNT_MODEL.BY_AMOUNT:
                self.cash_available += order.price * order.amount
        elif order.towards in [
                ORDER_DIRECTION.SELL, ORDER_DIRECTION.SELL_CLOSE,
                ORDER_DIRECTION.SELL_OPEN
        ]:
            self.sell_available[order.code] += order.amount

        # self.sell_available[]
    @property
    def close_positions_order(self):
        """平仓单

        Raises:
            RuntimeError -- if ACCOUNT.RUNNING_ENVIRONMENT is NOT TZERO

        Returns:
            list -- list with order
        """

        order_list = []
        time = '{} 15:00:00'.format(self.date)
        if self.running_environment == RUNNING_ENVIRONMENT.TZERO:
            for code, amount in self.hold_available.iteritems():
                order = False
                if amount < 0:
                    # 先卖出的单子 买平
                    order = self.send_order(
                        code=code,
                        price=0,
                        amount=abs(amount),
                        time=time,
                        towards=ORDER_DIRECTION.BUY_CLOSE,
                        order_model=ORDER_MODEL.CLOSE,
                        amount_model=AMOUNT_MODEL.BY_AMOUNT)
                elif amount > 0:
                    # 先买入的单子, 卖平
                    order = self.send_order(
                        code=code,
                        price=0,
                        amount=abs(amount),
                        time=time,
                        towards=ORDER_DIRECTION.SELL_CLOSE,
                        order_model=ORDER_MODEL.CLOSE,
                        amount_model=AMOUNT_MODEL.BY_AMOUNT)
                if order:
                    order_list.append(order)
            return order_list
        else:
            raise RuntimeError(
                'QAACCOUNT with {} environments cannot use this methods'.
                format(self.running_environment))

    def settle(self):
        """
        股票/期货的日结算

        股票的结算:  结转股票可卖额度
        T0的结算: 结转T0的额度

        期货的结算: 结转静态资金

        """

        if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum(
        ) != 0:
            raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format(
                self.hold_available.to_dict()))
        if self.market_type == MARKET_TYPE.FUTURE_CN:

            self.static_balance['frozen'].append(
                sum([
                    rx['money'] * rx['amount'] for var in self.frozen.values()
                    for rx in var.values()
                ]))

            self.static_balance['cash'].append(self.cash[-1])
            self.static_balance['hold'].append(self.hold.to_dict())
            self.static_balance['date'].append(self.date)
            """静态权益的结算

            只关心开仓价/ 不做盯市制度

            动态权益的结算需要关心

            """

            self.static_balance['static_assets'].append(
                self.static_balance['cash'][-1] +
                self.static_balance['frozen'][-1])

        self.sell_available = self.hold
        self.buy_available = self.hold
        self.datetime = '{} 09:30:00'.format(QA_util_get_next_day(
            self.date)) if self.date is not None else None

    def on_bar(self, event):
        '''
        策略事件
        :param event:
        :return:
        '''
        'while updating the market data'

        print("on_bar account {} ".format(self.account_cookie),
              event.market_data)

    def on_tick(self, event):
        '''
        策略事件
        :param event:
        :return:
        '''
        'on tick event'
        print("on_tick ", event.market_data)
        pass

    def from_message(self, message):
        """resume the account from standard message
        这个是从数据库恢复账户时需要的"""
        self.account_cookie = message.get('account_cookie', None)
        self.portfolio_cookie = message.get('portfolio_cookie', None)
        self.user_cookie = message.get('user_cookie', None)
        self.broker = message.get('broker', None)
        self.market_type = message.get('market_type', None)
        self.strategy_name = message.get('strategy_name', None)
        self._currenttime = message.get('current_time', None)
        self.allow_sellopen = message.get('allow_sellopen', False)
        self.allow_t0 = message.get('allow_t0', False)
        self.margin_level = message.get('margin_level', False)
        self.init_cash = message.get('init_cash',
                                     message.get('init_assets',
                                                 1000000))  # 兼容修改
        self.init_hold = pd.Series(message.get('init_hold', {}), name='amount')
        self.init_hold.index.name = 'code'
        self.commission_coeff = message.get('commission_coeff', 0.00015)
        self.tax_coeff = message.get('tax_coeff', 0.0015)
        self.history = message['history']
        self.cash = message['cash']
        self.time_index = message['trade_index']
        self.running_time = message.get('running_time', None)
        self.quantaxis_version = message.get('quantaxis_version', None)
        self.running_environment = message.get('running_environment',
                                               RUNNING_ENVIRONMENT.BACKETEST)
        self.settle()
        return self

    @property
    def table(self):
        """
        打印出account的内容
        """
        return pd.DataFrame([
            self.message,
        ]).set_index('account_cookie', drop=False).T

    def run(self, event):
        '''
        这个方法是被 QA_ThreadEngine 处理队列时候调用的, QA_Task 中 do 方法调用 run (在其它线程中)
       'QA_WORKER method 重载'
        :param event: 事件类型 QA_Event
        :return:
        '''
        'QA_WORKER method'
        if event.event_type is ACCOUNT_EVENT.SETTLE:
            self.settle()

        # elif event.event_type is ACCOUNT_EVENT.UPDATE:
        #     self.receive_deal(event.message)
        elif event.event_type is ACCOUNT_EVENT.MAKE_ORDER:
            """generate order
            if callback callback the order
            if not return back the order
            """
            data = self.send_order(code=event.code,
                                   amount=event.amount,
                                   time=event.time,
                                   amount_model=event.amount_model,
                                   towards=event.towards,
                                   price=event.price,
                                   order_model=event.order_model)
            if event.callback:
                event.callback(data)
            else:
                return data
        elif event.event_type is ENGINE_EVENT.UPCOMING_DATA:
            """update the market_data
            1. update the inside market_data struct
            2. tell the on_bar methods

            # 这样有点慢


            """

            self._currenttime = event.market_data.datetime[0]
            if self._market_data is None:
                self._market_data = event.market_data
            else:
                self._market_data = self._market_data + event.market_data
            self.on_bar(event)

            if event.callback:
                event.callback(event)

    def save(self):
        """
        存储账户信息
        """
        save_account(self.message)

    def sync_account(self, sync_message):
        """同步账户

        Arguments:
            sync_message {[type]} -- [description]
        """

        self.init_hold = sync_message['hold_available']
        self.init_cash = sync_message['cash_available']

        self.sell_available = copy.deepcopy(self.init_hold)
        self.history = []
        self.cash = [self.init_cash]
        self.cash_available = self.cash[-1]  # 在途资金

    def change_cash(self, money):
        """
        外部操作|高危|
        """
        res = self.cash[-1] + money
        if res >= 0:
            # 高危操作
            self.cash[-1] = res

    def get_orders(self, if_today=True):
        '''
        返回当日委托/历史委托
        :param if_today: true 只返回今天的订单
        :return: QA_OrderQueue
        '''
        # 🛠todo 筛选其它不是今天的订单返回
        return self.orders

    def get_history(self, start, end):
        """返回历史成交

        Arguments:
            start {str} -- [description]
            end {str]} -- [description]
        """
        return self.history_table.set_index('datetime', drop=False).loc[slice(
            pd.Timestamp(start), pd.Timestamp(end))]
Example #3
0
class QA_Position():
    """一个持仓模型:/兼容股票期货/兼容保证金持仓

    兼容快期DIFF协议

    基础字段

    code [str]  品种名称 
    volume_long_today [float] 今仓 多单持仓
    volume_long_his   [float] 昨仓 多单持仓
    volume_short_today [float] 今仓 空单持仓
    volume_short_his  [float] 昨仓 空单持仓

    volume_long_frozen_his [float] 多单昨日冻结
    volume_long_frozen_today [float] 多单今日冻结
    volume_short_frozen_his [float] 空单昨日冻结
    volume_short_frozen_today [float] 空单今日冻结

    margin_long  [float] 多单保证金
    margin_short [float] 空单保证金

    open_price_long [float] 多单开仓价
    open_price_short [float] 空单开仓价
    position_price_long [float] 逐日盯市的前一交易日的结算价
    position_price_short [float] 逐日盯市的前一交易日的结算价

    open_cost_long [float] 多单开仓成本(指的是保证金冻结)
    open_cost_short [float] 空单开仓成本
    position_cost_long [float] 多单持仓成本(指的是基于逐日盯市制度下的成本价)
    position_cost_short [float] 空单持仓成本


    market_type=MARKET_TYPE.STOCK_CN [enum] 市场类别
    exchange_id=EXCHANGE_ID.SZSE [enum] 交易所id(支持股票/期货)
    name=None [str] 名称


    功能:

    1/ 支持当价格变化后的 持仓的自行计算更新
    2/ 支持调仓模型(未加入)
    3/ 支持仓位风控(未加入)
    4/ 支持资金分配和PMS内部资金结算(moneypreset)

    PMS 内部可以预分配一个资金限额, 方便pms实时计算属于PMS的收益

    兼容QA_Account的创建/拆入Positions库

    QAPosition 不对订单信息做正确性保证, 需要自行在外部构建 OMS系统 {QACEPEngine/QAAccountPro}

    """
    def __init__(
            self,
            code='000001',
            account_cookie='quantaxis',
            portfolio_cookie='portfolio',
            username='******',
            moneypreset=100000,  # 初始分配资金
            frozen=None,
            moneypresetLeft=None,
            volume_long_today=0,
            volume_long_his=0,
            volume_short_today=0,
            volume_short_his=0,
            volume_long_frozen_his=0,
            volume_long_frozen_today=0,
            volume_short_frozen_his=0,
            volume_short_frozen_today=0,
            margin_long=0,
            margin_short=0,
            open_price_long=0,
            open_price_short=0,
            position_price_long=0,  # 逐日盯市的前一交易日的结算价
            position_price_short=0,  # 逐日盯市的前一交易日的结算价
            open_cost_long=0,
            open_cost_short=0,
            position_cost_long=0,
            position_cost_short=0,
            position_id=None,
            market_type=None,
            exchange_id=None,
            trades=None,
            orders=None,
            name=None,
            commission=0,
            auto_reload=False,
            allow_exceed=False,
            spms_id=None,
            oms_id=None,
            *args,
            **kwargs):

        self.code = code
        self.account_cookie = account_cookie
        self.portfolio_cookie = portfolio_cookie
        self.username = username
        self.time = ''
        self.market_preset = MARKET_PRESET().get_code(self.code)
        self.position_id = str(
            uuid.uuid4()) if position_id is None else position_id
        self.moneypreset = moneypreset
        self.moneypresetLeft = self.moneypreset if moneypresetLeft is None else moneypresetLeft
        """{'name': '原油',
            'unit_table': 1000,
            'price_tick': 0.1,
            'buy_frozen_coeff': 0.1,
            'sell_frozen_coeff': 0.1,
            'exchange': 'INE',
            'commission_coeff_peramount': 0,
            'commission_coeff_pervol': 20.0,
            'commission_coeff_today_peramount': 0,
            'commission_coeff_today_pervol': 0.0}
        """
        self.rule = 'FIFO'
        self.name = name

        if market_type is None:

            self.market_type = MARKET_TYPE.FUTURE_CN if re.search(
                r'[a-zA-z]+', self.code) else MARKET_TYPE.STOCK_CN
        self.exchange_id = exchange_id

        self.volume_long_his = volume_long_his
        self.volume_long_today = volume_long_today
        self.volume_short_his = volume_short_his
        self.volume_short_today = volume_short_today

        self.volume_long_frozen_his = volume_long_frozen_his
        self.volume_long_frozen_today = volume_long_frozen_today
        self.volume_short_frozen_his = volume_short_frozen_his
        self.volume_short_frozen_today = volume_short_frozen_today

        self.margin_long = margin_long
        self.margin_short = margin_short

        self.open_price_long = open_price_long
        self.open_price_short = open_price_short

        self.position_price_long = open_price_long if position_price_long == 0 else position_price_long
        self.position_price_short = open_price_short if position_price_short == 0 else position_price_short

        self.open_cost_long = open_cost_long if open_cost_long != 0 else open_price_long * \
            self.volume_long*self.market_preset.get('unit_table', 1)
        self.open_cost_short = open_cost_short if open_cost_short != 0 else open_price_short * \
            self.volume_short*self.market_preset.get('unit_table', 1)
        self.position_cost_long = position_cost_long if position_cost_long != 0 else self.position_price_long * \
            self.volume_long*self.market_preset.get('unit_table', 1)
        self.position_cost_short = position_cost_short if position_cost_short != 0 else self.position_price_short * \
            self.volume_short*self.market_preset.get('unit_table', 1)

        self.last_price = 0
        self.commission = commission
        self.trades = [] if trades is None else trades
        self.orders = {} if orders is None else orders
        self.frozen = {} if frozen is None else frozen
        self.spms_id = spms_id
        self.oms_id = oms_id
        if auto_reload:
            self.reload()
        self.allow_exceed = allow_exceed

    def __repr__(self):
        return '< QAPOSITION {} amount {}/{} >'.format(self.code,
                                                       self.volume_long,
                                                       self.volume_short)

    def read_diff(self, diff_slice):
        """[summary]

        Arguments:
            diff_slice {dict} -- [description]

            {'user_id': '100002',
            'exchange_id': 'SHFE',
            'instrument_id': 'rb1905',
            'volume_long_today': 0,
            'volume_long_his': 0,
            'volume_long': 0,
            'volume_long_frozen_today': 0,
            'volume_long_frozen_his': 0,
            'volume_long_frozen': 0,
            'volume_short_today': 0,
            'volume_short_his': 0,
            'volume_short': 0,
            'volume_short_frozen_today': 0,
            'volume_short_frozen_his': 0,
            'volume_short_frozen': 0,
            'open_price_long': 4193.0,
            'open_price_short': 4192.0,
            'open_cost_long': 0.0,
            'open_cost_short': 0.0,
            'position_price_long': 4193.0,
            'position_price_short': 4192.0,
            'position_cost_long': 0.0,
            'position_cost_short': 0.0,
            'last_price': 4137.0,
            'float_profit_long': 0.0,
            'float_profit_short': 0.0,
            'float_profit': 0.0,
            'position_profit_long': 0.0,
            'position_profit_short': 0.0,
            'position_profit': 0.0,
            'margin_long': 0.0,
            'margin_short': 0.0,
            'margin': 0.0}

        Returns:
            QA_Position -- [description]
        """
        self.account_cookie = diff_slice['user_id']
        self.code = diff_slice['instrument_id']
        self.volume_long_today = diff_slice['volume_long_today']
        self.volume_long_his = diff_slice['volume_long_his']
        self.volume_long_frozen_today = diff_slice['volume_long_frozen_today']
        self.volume_long_frozen_his = diff_slice['volume_long_frozen_his']
        self.volume_short_today = diff_slice['volume_short_today']
        self.volume_short_his = diff_slice['volume_short_his']
        self.volume_short_frozen_today = diff_slice[
            'volume_short_frozen_today']
        self.volume_short_frozen_his = diff_slice['volume_short_frozen_his']
        self.open_price_long = diff_slice['open_price_long']
        self.open_price_short = diff_slice['open_price_short']
        self.open_cost_long = diff_slice['open_cost_long']
        self.open_cost_short = diff_slice['open_cost_short']
        self.position_price_long = diff_slice['position_price_long']
        self.position_price_short = diff_slice['position_price_short']
        self.position_cost_long = diff_slice['position_cost_long']
        self.position_cost_short = diff_slice['position_cost_short']
        self.margin_long = diff_slice['margin_long']
        self.margin_short = diff_slice['margin_short']
        self.exchange_id = diff_slice['exchange_id']
        self.market_type = MARKET_TYPE.FUTURE_CN
        return self

    @property
    def volume_long(self):
        return self.volume_long_today + self.volume_long_his

    @property
    def volume_short(self):
        return self.volume_short_his + self.volume_short_today

    @property
    def volume_long_frozen(self):
        return self.volume_long_frozen_his + self.volume_long_frozen_today

    @property
    def volume_short_frozen(self):
        return self.volume_short_frozen_his + self.volume_short_frozen_today

    @property
    def margin(self):
        return self.margin_long + self.margin_short

    @property
    def float_profit_long(self):
        if self.market_preset is not None:
            return self.last_price * self.volume_long * self.market_preset.get(
                'unit_table', 1) - self.open_cost_long

    @property
    def float_profit_short(self):
        if self.market_preset is not None:
            return self.open_cost_short - self.last_price * self.volume_short * self.market_preset.get(
                'unit_table', 1)

    @property
    def float_profit(self):
        return self.float_profit_long + self.float_profit_short

    @property
    def position_profit_long(self):
        if self.market_preset is not None:
            return self.last_price * self.volume_long * self.market_preset.get(
                'unit_table', 1) - self.position_cost_long

    @property
    def position_profit_short(self):
        if self.market_preset is not None:
            return self.position_cost_short - self.last_price * self.volume_short * self.market_preset.get(
                'unit_table', 1)

    @property
    def position_profit(self):
        return self.position_profit_long + self.position_profit_short

    @property
    def static_message(self):
        return {
            # 基础字段
            'code': self.code,  # 品种名称
            'instrument_id': self.code,
            'user_id': self.account_cookie,
            'portfolio_cookie': self.portfolio_cookie,
            'username': self.username,
            'position_id': self.position_id,
            'account_cookie': self.account_cookie,
            'frozen': self.frozen,
            'name': self.name,
            'spms_id': self.spms_id,
            'oms_id': self.oms_id,
            'market_type': self.market_type,
            'exchange_id': self.exchange_id,  # 交易所ID
            'moneypreset': self.moneypreset,
            'moneypresetLeft': self.moneypresetLeft,
            'lastupdatetime': str(self.time),
            # 持仓量
            'volume_long_today': self.volume_long_today,
            'volume_long_his': self.volume_long_his,
            'volume_long': self.volume_long,
            'volume_short_today': self.volume_short_today,
            'volume_short_his': self.volume_short_his,
            'volume_short': self.volume_short,
            # 平仓委托冻结(未成交)
            'volume_long_frozen_today': self.volume_long_frozen_today,
            'volume_long_frozen_his': self.volume_long_frozen_his,
            'volume_long_frozen': self.volume_long_frozen,
            'volume_short_frozen_today': self.volume_short_frozen_today,
            'volume_short_frozen_his': self.volume_short_frozen_his,
            'volume_short_frozen': self.volume_short_frozen,
            # 保证金
            'margin_long': self.margin_long,  # 多头保证金
            'margin_short': self.margin_short,
            'margin': self.margin,
            # 持仓字段
            'position_price_long': self.position_price_long,  # 多头成本价
            'position_cost_long': self.position_cost_long,  # 多头总成本(  总市值)
            'position_price_short': self.position_price_short,
            'position_cost_short': self.position_cost_short,
            # 平仓字段
            'open_price_long': self.open_price_long,  # 多头开仓价
            'open_cost_long': self.open_cost_long,  # 多头开仓成本
            'open_price_short': self.open_price_short,  # 空头开仓价
            'open_cost_short': self.open_cost_short,  # 空头成本
            # 历史字段
            'trades': self.trades,
            'orders': self.orders
        }

    @property
    def hold_detail(self):
        return {
            # 持仓量
            'volume_long_today': self.volume_long_today,
            'volume_long_his': self.volume_long_his,
            'volume_long': self.volume_long,
            'volume_short_today': self.volume_short_today,
            'volume_short_his': self.volume_short_his,
            'volume_short': self.volume_short
        }

    @property
    def realtime_message(self):
        return {
            # 扩展字段
            "last_price": self.last_price,
            # //多头浮动盈亏  ps.last_price * ps.volume_long * ps.ins->volume_multiple - ps.open_cost_long;
            "float_profit_long": self.float_profit_long,
            # //空头浮动盈亏  ps.open_cost_short - ps.last_price * ps.volume_short * ps.ins->volume_multiple;
            "float_profit_short": self.float_profit_short,
            # //浮动盈亏 = float_profit_long + float_profit_short
            "float_profit": self.float_profit,
            "position_profit_long": self.position_profit_long,  # //多头持仓盈亏
            "position_profit_short": self.position_profit_short,  # //空头持仓盈亏
            # //持仓盈亏 = position_profit_long + position_profit_short
            "position_profit": self.position_profit
        }

    @property
    def message(self):
        msg = self.static_message
        msg.update(self.realtime_message)
        return msg

    def order_check(self, amount: float, price: float, towards: int,
                    order_id: str) -> bool:
        res = False
        if towards == ORDER_DIRECTION.BUY_CLOSE:
            # print('buyclose')
            #print(self.volume_short - self.volume_short_frozen)
            # print(amount)
            if (self.volume_short - self.volume_short_frozen) >= amount:
                # check
                self.volume_short_frozen_today += amount
                res = True
            else:
                print('BUYCLOSE 仓位不足')

        elif towards == ORDER_DIRECTION.BUY_CLOSETODAY:
            if (self.volume_short_today -
                    self.volume_short_frozen_today) >= amount:
                self.volume_short_frozen_today += amount
                res = True
            else:
                print('BUYCLOSETODAY 今日仓位不足')
        elif towards == ORDER_DIRECTION.SELL_CLOSE:
            # print('sellclose')
            #print(self.volume_long - self.volume_long_frozen)
            # print(amount)
            if (self.volume_long - self.volume_long_frozen) >= amount:
                self.volume_long_frozen_today += amount
                res = True
            else:
                print('SELL CLOSE 仓位不足')

        elif towards == ORDER_DIRECTION.SELL_CLOSETODAY:
            if (self.volume_long_today -
                    self.volume_short_frozen_today) >= amount:
                # print('sellclosetoday')
                #print(self.volume_long_today - self.volume_long_frozen)
                # print(amount)
                self.volume_long_frozen_today += amount
                return True
            else:
                print('SELLCLOSETODAY 今日仓位不足')
        elif towards in [
                ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN,
                ORDER_DIRECTION.BUY
        ]:
            """
            冻结的保证金
            """
            moneyneed = float(amount) * float(price) * float(
                self.market_preset.get('unit_table', 1)) * float(
                    self.market_preset.get('buy_frozen_coeff', 1))

            if (self.moneypresetLeft > moneyneed) or self.allow_exceed:
                self.moneypresetLeft -= moneyneed
                self.frozen[order_id] = moneyneed
                res = True
            else:
                print('开仓保证金不足 TOWARDS{} Need{} HAVE{}'.format(
                    towards, moneyneed, self.moneypresetLeft))

        return res

    def send_order(self, amount: float, price: float, towards: int):
        order_id = str(uuid.uuid4())
        if self.order_check(amount, price, towards, order_id):
            #print('order check success')
            order = {
                'position_id': str(self.position_id),
                'account_cookie': self.account_cookie,
                'instrument_id': self.code,
                'towards': int(towards),
                'exchange_id': str(self.exchange_id),
                'order_time': str(self.time),
                'volume': float(amount),
                'price': float(price),
                'order_id': order_id,
                'status': ORDER_STATUS.NEW
            }
            self.orders[order_id] = order
            return order
        else:
            print(RuntimeError('ORDER CHECK FALSE: {}'.format(self.code)))
            return False

    def update_pos(self, price, amount, towards):
        """支持股票/期货的更新仓位

        Arguments:
            price {[type]} -- [description]
            amount {[type]} -- [description]
            towards {[type]} -- [description]

            margin: 30080
            margin_long: 0
            margin_short: 30080
            open_cost_long: 0
            open_cost_short: 419100
            open_price_long: 4193
            open_price_short: 4191
            position_cost_long: 0
            position_cost_short: 419100
            position_price_long: 4193
            position_price_short: 4191
            position_profit: -200
            position_profit_long: 0
            position_profit_short: -200
        """
        self.on_pirce_change(price)
        temp_cost = float(amount)*float(price) * \
            float(self.market_preset.get('unit_table', 1))
        profit = 0
        if towards == ORDER_DIRECTION.BUY:
            # 股票模式/ 期货买入开仓
            marginValue = temp_cost
            self.margin_long += marginValue
            # 重算开仓均价
            self.open_price_long = (self.open_price_long * self.volume_long +
                                    amount * price) / (amount +
                                                       self.volume_long)
            # 重算持仓均价
            self.position_price_long = (
                self.position_price_long * self.volume_long +
                amount * price) / (amount + self.volume_long)
            # 增加今仓数量 ==> 会自动增加volume_long
            self.volume_long_today += amount
            #
            self.open_cost_long += temp_cost
            self.position_cost_long += temp_cost

        elif towards == ORDER_DIRECTION.SELL:
            # 股票卖出模式:
            # 今日买入仓位不能卖出
            if self.volume_long_his > amount:

                self.position_cost_long = self.position_cost_long * \
                    (self.volume_long - amount)/self.volume_long
                self.open_cost_long = self.open_cost_long * \
                    (self.volume_long-amount)/self.volume_long

                self.volume_long_his -= amount

                self.volume_long_frozen_today -= amount
                marginValue = -1 * (self.position_price_long * amount)
                profit = (price - self.position_price_long) * amount
                self.moneypresetLeft += (-marginValue + profit)

        elif towards == ORDER_DIRECTION.BUY_OPEN:

            # 增加保证金
            marginValue = temp_cost * \
                self.market_preset['buy_frozen_coeff']
            self.margin_long += marginValue
            # 重算开仓均价
            self.open_price_long = (self.open_price_long * self.volume_long +
                                    amount * price) / (amount +
                                                       self.volume_long)
            # 重算持仓均价
            self.position_price_long = (
                self.position_price_long * self.volume_long +
                amount * price) / (amount + self.volume_long)
            # 增加今仓数量 ==> 会自动增加volume_long
            self.volume_long_today += amount
            #
            self.open_cost_long += temp_cost
            self.position_cost_long += temp_cost
            self.moneypresetLeft -= marginValue

        elif towards == ORDER_DIRECTION.SELL_OPEN:
            # 增加保证金
            """
            1. 增加卖空保证金
            2. 重新计算 开仓成本
            3. 重新计算 持仓成本
            4. 增加开仓cost
            5. 增加持仓cost
            6. 增加空单仓位
            """
            marginValue = temp_cost * \
                self.market_preset['sell_frozen_coeff']
            self.margin_short += marginValue
            # 重新计算开仓/持仓成本
            self.open_price_short = (
                self.open_price_short * self.volume_short +
                amount * price) / (amount + self.volume_short)
            self.position_price_short = (
                self.position_price_short * self.volume_short +
                amount * price) / (amount + self.volume_short)
            self.open_cost_short += temp_cost
            self.position_cost_short += temp_cost
            self.volume_short_today += amount
            self.moneypresetLeft -= marginValue

        elif towards == ORDER_DIRECTION.BUY_CLOSETODAY:
            if self.volume_short_today > amount:
                self.position_cost_short = self.position_cost_short * \
                    (self.volume_short-amount)/self.volume_short
                self.open_cost_short = self.open_cost_short * \
                    (self.volume_short-amount)/self.volume_short
                self.volume_short_today -= amount
                self.volume_short_frozen_today += amount
                # close_profit = (self.position_price_short - price) * volume * position->ins->volume_multiple;
                marginValue = -(self.position_price_short * amount*self.market_preset.get('unit_table') *\
                    self.market_preset['sell_frozen_coeff'])
                profit = (self.position_price_short - price
                          ) * amount * self.market_preset.get('unit_table')

                self.moneypresetLeft += (-marginValue + profit)

                # 释放保证金
                # TODO
                # self.margin_short
                #self.open_cost_short = price* amount

        elif towards == ORDER_DIRECTION.SELL_CLOSETODAY:
            if self.volume_long_today > amount:
                self.position_cost_long = self.position_cost_long * \
                    (self.volume_long - amount)/self.volume_long
                self.open_cost_long = self.open_cost_long * \
                    (self.volume_long-amount)/self.volume_long
                self.volume_long_today -= amount
                self.volume_long_frozen_today += amount

                marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\
                    self.market_preset['buy_frozen_coeff'])
                profit = (price - self.position_price_long) * \
                    amount * self.market_preset.get('unit_table')
                self.moneypresetLeft += (-marginValue + profit)

        elif towards == ORDER_DIRECTION.BUY_CLOSE:
            # 有昨仓先平昨仓
            self.position_cost_short = self.position_cost_short * \
                (self.volume_short-amount)/self.volume_short
            self.open_cost_short = self.open_cost_short * \
                (self.volume_short-amount)/self.volume_short
            if self.volume_short_his >= amount:
                self.volume_short_his -= amount
            else:
                self.volume_short_today -= (amount - self.volume_short_his)
                self.volume_short_his = 0
            self.volume_short_frozen_today -= amount

            marginValue = -1*(self.position_price_short * amount*self.market_preset.get('unit_table') *\
                self.market_preset['sell_frozen_coeff'])
            profit = (self.position_price_short -
                      price) * amount * self.market_preset.get('unit_table')

            self.moneypresetLeft += (-marginValue + profit)
        elif towards == ORDER_DIRECTION.SELL_CLOSE:
            # 有昨仓先平昨仓
            self.position_cost_long = self.position_cost_long * \
                (self.volume_long - amount)/self.volume_long
            self.open_cost_long = self.open_cost_long * \
                (self.volume_long-amount)/self.volume_long
            if self.volume_long_his >= amount:
                self.volume_long_his -= amount
            else:
                self.volume_long_today -= (amount - self.volume_long_his)
                self.volume_long_his = 0
            self.volume_long_frozen_today -= amount
            marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\
                self.market_preset['buy_frozen_coeff'])
            profit = (price - self.position_price_long) * \
                amount * self.market_preset.get('unit_table')
            self.moneypresetLeft += (-marginValue + profit)
        # 计算收益/成本

        return marginValue, profit

    def settle(self):
        """收盘后的结算事件
        """
        self.volume_long_his += self.volume_long_today
        self.volume_long_today = 0
        self.volume_long_frozen_today = 0
        self.volume_short_his += self.volume_short_today
        self.volume_short_today = 0
        self.volume_short_frozen_today = 0

    @property
    def curpos(self):
        return {
            'volume_long': self.volume_long,
            'volume_short': self.volume_short,
            'volume_long_frozen': self.volume_long_frozen,
            'volume_short_frozen': self.volume_short_frozen
        }

    @property
    def close_available(self):
        """可平仓数量

        Returns:
            [type] -- [description]
        """
        return {
            'volume_long': self.volume_long - self.volume_long_frozen,
            'volume_short': self.volume_short - self.volume_short_frozen
        }

    def change_moneypreset(self, money):
        self.moneypreset = money

    def save(self):
        """save&update

        save data to mongodb | update
        """
        print(self.static_message)
        save_position(self.static_message)

    def reload(self):
        res = DATABASE.positions.find_one({
            'account_cookie': self.account_cookie,
            'portfolio_cookie': self.portfolio_cookie,
            'username': self.username,
            'position_id': self.position_id
        })
        if res is None:
            self.save()
        else:
            self.loadfrommessage(res)

    def calc_commission(
        self,
        trade_price,
        trade_amount,
        trade_towards,
    ):
        if self.market_type == MARKET_TYPE.FUTURE_CN:
            # 期货不收税
            # 双边手续费 也没有最小手续费限制
            value = trade_price * trade_amount * \
                self.market_preset.get_unit(code)

            commission_fee_preset = self.market_preset.get_code(code)
            if trade_towards in [
                    ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE,
                    ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN
            ]:
                commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_peramount'] * \
                    abs(value)
            elif trade_towards in [
                    ORDER_DIRECTION.BUY_CLOSETODAY,
                    ORDER_DIRECTION.SELL_CLOSETODAY
            ]:
                commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_today_peramount'] * \
                    abs(value)
            return commission_fee

    def loadfrommessage(self, message):
        self.__init__(
            code=message['code'],
            account_cookie=message['account_cookie'],
            frozen=message['frozen'],
            portfolio_cookie=message['portfolio_cookie'],
            username=message['username'],
            moneypreset=message['moneypreset'],  # 初始分配资金
            moneypresetLeft=message['moneypresetLeft'],
            volume_long_today=message['volume_long_today'],
            volume_long_his=message['volume_long_his'],
            volume_short_today=message['volume_short_today'],
            volume_short_his=message['volume_short_his'],
            volume_long_frozen_his=message['volume_long_frozen_his'],
            volume_long_frozen_today=message['volume_long_frozen_today'],
            volume_short_frozen_his=message['volume_short_frozen_his'],
            volume_short_frozen_today=message['volume_short_frozen_today'],
            margin_long=message['margin_long'],
            margin_short=message['margin_short'],
            open_price_long=message['open_price_long'],
            open_price_short=message['open_price_short'],
            # 逐日盯市的前一交易日的结算价
            position_price_long=message['position_price_long'],
            # 逐日盯市的前一交易日的结算价
            position_price_short=message['position_price_short'],
            open_cost_long=message['open_cost_long'],
            open_cost_short=message['open_cost_short'],
            position_cost_long=message['position_cost_long'],
            position_cost_short=message['position_cost_short'],
            position_id=message['position_id'],
            market_type=message['market_type'],
            exchange_id=message['exchange_id'],
            trades=message['trades'],
            orders=message['orders'],
            commission=message['commission'],
            name=message['name'])

        return self

    def on_order(self, order: QA_Order):
        """这里是一些外部操作导致的POS变化

        - 交易过程的外部手动交易
        - 风控状态下的监控外部交易

        order_id 是外部的
        trade_id 不一定存在
        """

        if order['order_id'] not in self.frozen.keys():
            print('OUTSIDE ORDER')
            # self.frozen[order['order_id']] = order[]
            # 回放订单/注册进订单系统
            order = self.send_order(
                order.get('amount', order.get('volume')), order['price'],
                eval('ORDER_DIRECTION.{}_{}'.format(order.get('direction'),
                                                    order.get('offset'))))
            self.orders[order]['status'] = ORDER_STATUS.QUEUED

    def on_transaction(self, transaction: dict):
        towards = transaction.get(
            'towards',
            eval('ORDER_DIRECTION.{}_{}'.format(transaction.get('direction'),
                                                transaction.get('offset'))))
        transaction['towards'] = towards
        # TODO:
        # 在这里可以加入更多关于PMS交易的代码
        try:
            self.update_pos(
                transaction['price'],
                transaction.get('amount', transaction.get('volume')), towards)
            self.moneypresetLeft += self.frozen.get(transaction['order_id'], 0)
            # 当出现外部交易的时候, 直接在frozen中注册订单
            self.frozen[transaction['order_id']] = 0
            self.orders[transaction['order_id']] = ORDER_STATUS.SUCCESS_ALL
            self.trades.append(transaction)
        except Exception as e:
            raise e

    def on_pirce_change(self, price):
        self.last_price = price

    def on_bar(self, bar):
        """只订阅这个code的数据

        Arguments:
            bar {[type]} -- [description]
        """
        self.last_price = bar['close']
        # print(self.realtime_message)
        pass

    def on_tick(self, tick):
        """只订阅当前code的tick

        Arguments:
            tick {[type]} -- [description]
        """
        self.last_price = tick['LastPrice']
        # print(self.realtime_message)
        pass

    def on_signal(self, signal):
        raise NotImplementedError('此接口为内部接口 为CEP专用')

    def callback_sub(self):
        raise NotImplementedError('此接口为内部接口 为CEP专用')

    def callback_pub(self):
        raise NotImplementedError('此接口为内部接口 为CEP专用')