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

            commission_fee_preset = MARKET_PRESET().get_code(self.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
Beispiel #2
0
    def __init__(self, target):

        self.target = target
        self._style_title = [
            'beta', 'momentum', 'size', 'earning_yield', 'volatility',
            'growth', 'value', 'leverage', 'liquidity', 'reversal'
        ]
        self.market_preset = MARKET_PRESET()
        self.pnl = self.pnl_fifo
Beispiel #3
0
    def __init__(self, target):

        self.target = target
        self._style_title = [
            'beta',
            'momentum',
            'size',
            'earning_yield',
            'volatility',
            'growth',
            'value',
            'leverage',
            'liquidity',
            'reversal'
        ]
        self.market_preset = MARKET_PRESET()
        self.pnl = self.pnl_fifo
Beispiel #4
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='******',
                 user_cookie = '',
                 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.user_cookie = user_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 + self.volume_long_frozen
    @property
    def volume_short(self):
        return self.volume_short_his + self.volume_short_today+ self.volume_short_frozen

    @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,
            'user_cookie': self.user_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_price_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
            self.moneypresetLeft -= marginValue



        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)
                self.margin_long += marginValue
                profit = (price - self.position_price_long) * amount 
                self.moneypresetLeft += (-marginValue + profit)
            else:
                return 0,0

        elif towards == ORDER_DIRECTION.BUY_OPEN:

            # 增加保证金
            marginValue = temp_cost * \
                self.market_preset.get('buy_frozen_coeff',1)
            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'])

                self.margin_short += marginValue     
                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.get('buy_frozen_coeff',1))
                self.margin_long += marginValue     
                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',1) *\
                self.market_preset['sell_frozen_coeff'])
            profit = (self.position_price_short - price
                      ) * amount * self.market_preset.get('unit_table')
            self.margin_short += marginValue          

            self.moneypresetLeft += (-marginValue + profit)
        elif towards == ORDER_DIRECTION.SELL_CLOSE:
            # 有昨仓先平昨仓
            print(self.curpos)
            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',1) *\
                self.market_preset.get('buy_frozen_coeff',1))
            profit = (price - self.position_price_long) * \
                amount * self.market_preset.get('unit_table',1)
            self.margin_long += marginValue     
            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,
            'user_cookie': self.user_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 * \
                MARKET_PRESET().get_unit(self.code)

            commission_fee_preset = MARKET_PRESET().get_code(self.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'],
            user_cookie=message['user_cookie'],
            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'])
        if self.volume_long+ self.volume_short >0:
            self.last_price =( self.open_price_long*self.volume_long + self.open_price_short*self.volume_short)/(self.volume_long+ self.volume_short)
        else:
            self.last_price = 0
        
        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_price_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专用')
Beispiel #5
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))]
Beispiel #6
0
    def load_marketpreset(self):
        """加载市场表
        """

        self.market_preset = MARKET_PRESET()
Beispiel #7
0
    def __init__(self, username, password, model="SIM", broker_name="QAPaperTrading", trade_host=mongo_ip,
                 init_cash=1000000, taskid=str(uuid.uuid4()), nodatabase=False,
                 trade_datetime=str(datetime.datetime.now())):
        """Initial
        QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类


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

        使用 model = SIM/ REAL来切换

        qifiaccount 不去区分你的持仓是股票还是期货, 因此你可以实现跨市场的交易持仓管理
        nodatabase 离线模式
        """
        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).karma_backtest
        elif model == 'MANUAL':
            self.db = pymongo.MongoClient(trade_host).karma_manual
            self.db.history.create_index([("account_cookie", pymongo.DESCENDING),
                                          ("trading_day", pymongo.DESCENDING)],
                                         unique=True, name="account_cookie_trading_day")
        else:
            self.db = pymongo.MongoClient(trade_host).karma_realtime

        self.pub_host = ""
        self.trade_host = ""
        self.last_updatetime = trade_datetime
        self.status = 200
        self._trading_day = ""
        self.init_cash = init_cash
        self.pre_balance = 0
        self.datetime = trade_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 = {}
        self.market_preset = MARKET_PRESET()
        self.nodatabase = nodatabase
Beispiel #8
0
class QA_Order():
    '''
        记录order
    '''

    def __init__(
            self,
            price=None,
            date=None,
            datetime=None,
            sending_time=None,
            trade_time=False,
            amount=0,
            market_type=None,
            frequence=None,
            towards=None,
            code=None,
            user_cookie=None,
            account_cookie=None,
            strategy=None,
            order_model=None,
            money=None,
            amount_model=AMOUNT_MODEL.BY_AMOUNT,
            broker=None,
            order_id=None,
            trade_id=False,
            _status=ORDER_STATUS.NEW,
            callback=False,
            commission_coeff=0.00025,
            tax_coeff=0.001,
            exchange_id=None,
            position_id=None,
            *args,
            **kwargs
    ):
        '''




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


        :param args: type tuple
        :param kwargs: type dict

        # 2018-08-12 把order变成一个状态机>
        # 以前的order只是一个信息承载的工具,现在需要让他具备状态的方法

        NEW = 100
        SUCCESS_ALL = 200
        SUCCESS_PART = 203 # success_part 是部分成交 一个中间状态 剩余的订单还在委托队列中
        QUEUED = 300  # queued 用于表示在order_queue中 实际表达的意思是订单存活 待成交
        CANCEL = 400
        CANCEL_PART = 402 # cancel_part是部分撤单(及 下单后成交了一部分 剩余的被撤单 这是一个最终状态)
        SETTLED = 500
        FAILED = 600
        '''

        self.price = price
        self.datetime = None

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

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

        self.trade_price = 0                                       # 成交均价
        self.broker = broker
        self.callback = callback                                   # 委托成功的callback
        self.money = money                                         # 委托需要的金钱
        self.reason = None                                         # 原因列表
        self.exchange_id = exchange_id
        self.time_condition = 'GFD'                                # 当日有效
        self._status = _status

        self.position_id = position_id
        # 增加订单对于多账户以及多级别账户的支持 2018/11/12
        self.mainacc_id = None if 'mainacc_id' not in kwargs.keys(
        ) else kwargs['mainacc_id']
        self.subacc_id = None if 'subacc_id' not in kwargs.keys(
        ) else kwargs['subacc_id']
        self.direction = 'BUY' if self.towards in [
            ORDER_DIRECTION.BUY,
            ORDER_DIRECTION.BUY_OPEN,
            ORDER_DIRECTION.BUY_CLOSE
        ] else 'SELL'
        self.offset = 'OPEN' if self.towards in [
            ORDER_DIRECTION.BUY,
            ORDER_DIRECTION.BUY_OPEN,
            ORDER_DIRECTION.SELL_OPEN
        ] else 'CLOSE'

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

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

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

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

    @property
    def status(self):

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

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

    def calc_commission(self, trade_price, trade_amount):

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

            return max(commission_fee, 5)


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

    def cancel(self):
        """撤单

        Arguments:
            amount {int} -- 撤单数量
        """

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

    def failed(self, reason=None):
        """失败订单(未成功创建入broker)

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

    def trade(self, trade_id, trade_price, trade_amount, trade_time):
        """trade 状态

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

            if trade_amount < 1:

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

                    trade_time = str(trade_time)

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

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

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

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

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

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

    def callingback(self):
        """回调函数

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

        if self.callback:
            return self.callback

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

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

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

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

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

    def to_qatradegatway(self):
        """[summary]
        {'topic': 'sendorder', 
        'account_cookie': '100004', 
        'strategy_id': None, 
        'order_direction': 'SELL', 
        'order_offset': 'OPEN', 
        'code': 'rb1910', 
        'price': 3745.0, 
        'order_time': '2019-05-08 13:55:38.000000', 
        'exchange_id': 'SHFE', 
        'volume': 1.0, 
        'order_id': '5ab55219-adf6-432f-90db-f1bc5f29f4e5'}


        'topic': 'sendorder',
        'account_cookie': acc,
        'strategy_id': 'test',
        'code': code,
        'price': price[code],
        'order_direction': 'SELL',
        'order_offset': 'CLOSE',
        'volume': 1,
        'order_time': str(datetime.datetime.now()),
        'exchange_id': 'SHFE'
        """
        return {
            'topic': 'sendorder',
            'account_cookie': self.account_cookie,
            'strategy_id': self.strategy,
            'order_direction': self.direction,
            'order_offset': self.offset,
            'code': self.code.lower(),
            'price': self.price,
            'order_time': self.sending_time,
            'exchange_id': self.get_exchange(self.code),
            'volume': int(self.amount),
            'order_id': self.order_id
        }

    def to_qifi(self):

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

    def from_otgformat(self, otgOrder):
        """[summary]

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


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

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

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

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

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

            return self
        except Exception as e:
            QA_util_log_info('Failed to tran from dict {}'.format(e))
Beispiel #9
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的收益

    """
    def __init__(
            self,
            code='000001',
            account_cookie='quantaxis',
            moneypreset=100000,  # 初始分配资金
            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,
            market_type=MARKET_TYPE.STOCK_CN,
            exchange_id=EXCHANGE_ID.SZSE,
            name=None,
            *args,
            **kwargs):

        self.code = code
        self.account_cookie = account_cookie
        self.time = ''
        self.market_preset = MARKET_PRESET().get_code(self.code)
        self.position_id = str(uuid.uuid4())
        self.moneypreset = moneypreset
        self.moneypresetLeft = self.moneypreset
        """{'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
        self.market_type = market_type
        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.trades = {}

    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,
            'name': self.name,
            'market_type': self.market_type,
            'exchange_id': self.exchange_id,  # 交易所ID
            # 持仓量
            '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  # 空头成本
        }

    @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
        }

    def order_check(self, amount: float, price: float, towards: int) -> 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

        elif towards == ORDER_DIRECTION.BUY_CLOSETODAY and (
                self.volume_short_today -
                self.volume_short_frozen_today) >= amount:
            self.volume_short_frozen_today += amount
            res = True

        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

        elif towards == ORDER_DIRECTION.SELL_CLOSETODAY and (
                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
        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:
                self.moneypresetLeft -= moneyneed
                res = True

        return res

    def send_order(self, amount: float, price: float, towards: int):
        if self.order_check(amount, price, towards):
            print('order check success')

            return {
                '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': str(uuid.uuid4())
            }
        else:
            return RuntimeError('ORDER CHECK FALSE: {}'.format(self.code))

    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
        """

        temp_cost = float(amount)*float(price) * \
            float(self.market_preset.get('unit_table', 1))

        if towards == ORDER_DIRECTION.BUY:
            # 股票模式/ 期货买入开仓
            self.volume_long_today += amount
        elif towards == ORDER_DIRECTION.SELL:
            # 股票卖出模式:
            # 今日买入仓位不能卖出
            if self.volume_long_his > amount:
                self.volume_long_his -= amount

        elif towards == ORDER_DIRECTION.BUY_OPEN:

            # 增加保证金
            temp_margin = temp_cost * \
                self.market_preset['buy_frozen_coeff']
            self.margin_long += temp_margin
            # 重算开仓均价
            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 -= temp_margin

        elif towards == ORDER_DIRECTION.SELL_OPEN:
            # 增加保证金
            """
            1. 增加卖空保证金
            2. 重新计算 开仓成本
            3. 重新计算 持仓成本
            4. 增加开仓cost
            5. 增加持仓cost
            6. 增加空单仓位
            """

            self.margin_short += temp_cost * \
                self.market_preset['sell_frozen_coeff']
            # 重新计算开仓/持仓成本
            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

        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 = temp_cost * self.market_preset['buy_frozen_coeff']
                profit = (self.position_price_short - price
                          ) * amount * self.market_preset.get('unit_table')
                # 释放保证金
                # 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

        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
        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
        # 计算收益/成本

    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
        }

    @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):
        pass

    def reload(self):
        pass

    def on_order(self, order: QA_Order):
        pass

    def on_transaction(self, transaction: dict):
        towards = transaction.get(
            'towards',
            eval('ORDER_DIRECTION.{}_{}'.format(transaction.get('direction'),
                                                transaction.get('offset'))))
        #TODO:
        #在这里可以加入更多关于PMS交易的代码
        try:
            self.update_pos(
                transaction['price'],
                transaction.get('amount', transaction.get('volume')), towards)
        except Exception as e:
            raise e

        print(self.static_message)

    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专用')
Beispiel #10
0
    def __init__(self,
                 code='000001',
                 account_cookie='quantaxis',
                 moneypreset=100000,  # 初始分配资金
                 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_his=0,
                 margin_short_his=0,
                 margin_long_today=0,
                 margin_short_today=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,


                 market_type=MARKET_TYPE.STOCK_CN,
                 exchange_id=EXCHANGE_ID.SZSE,
                 name=None,

                 ):

        self.code = code
        self.account_cookie = account_cookie
        self.market_preset = MARKET_PRESET().get_code(self.code)
        self.position_id = uuid.uuid4()
        self.moneypreset = moneypreset
        """{'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
        self.market_type = market_type
        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_his = margin_long_his
        self.margin_short_his = margin_short_his
        self.margin_long_today = margin_long_today
        self.margin_short_today = margin_short_today

        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
Beispiel #11
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/ 支持仓位风控(未加入)

    """

    def __init__(self,
                 code='000001',
                 account_cookie='quantaxis',
                 moneypreset=100000,  # 初始分配资金
                 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_his=0,
                 margin_short_his=0,
                 margin_long_today=0,
                 margin_short_today=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,


                 market_type=MARKET_TYPE.STOCK_CN,
                 exchange_id=EXCHANGE_ID.SZSE,
                 name=None,

                 ):

        self.code = code
        self.account_cookie = account_cookie
        self.market_preset = MARKET_PRESET().get_code(self.code)
        self.position_id = uuid.uuid4()
        self.moneypreset = moneypreset
        """{'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
        self.market_type = market_type
        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_his = margin_long_his
        self.margin_short_his = margin_short_his
        self.margin_long_today = margin_long_today
        self.margin_short_today = margin_short_today

        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

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

    @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_long(self):
        return self.margin_long_his + self.margin_long_today

    @property
    def margin_short(self):
        return self.margin_short_his + self.margin_short_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,
            'name': self.name,
            'market_type': self.market_type,
            'exchange_id': self.exchange_id,  # 交易所ID
            # 持仓量
            '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  # 空头成本
        }

    @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
        }

    def receive_order(self, order:QA_Order):
        #self.update_pos(order.)
        pass

    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
        """
        temp_cost = amount*price * \
            self.market_preset.get('unit_table', 1)
        # if towards == ORDER_DIRECTION.SELL_CLOSE:
        if towards == ORDER_DIRECTION.BUY:
            # 股票模式/ 期货买入开仓
            self.volume_long_today += amount
        elif towards == ORDER_DIRECTION.SELL:
            # 股票卖出模式:
            # 今日买入仓位不能卖出
            if self.volume_long_his > amount:
                self.volume_long_his -= amount

        elif towards == ORDER_DIRECTION.BUY_OPEN:

            # 增加保证金
            self.margin_long += temp_cost * \
                self.market_preset['buy_frozen_coeff']
            # 重算开仓均价
            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

        elif towards == ORDER_DIRECTION.SELL_OPEN:
            # 增加保证金

            self.margin_short += temp_cost * \
                self.market_preset['sell_frozen_coeff']
            # 重新计算开仓/持仓成本
            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.volume_short_today += amount

        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
                # close_profit = (self.position_price_short - price) * volume * position->ins->volume_multiple;

                #self.volume_short_frozen_today += amount
                # 释放保证金
                # 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

        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
        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 -= amount

        # 计算收益/成本

    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
        }

    @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):
        pass

    def reload(self):
        pass

    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专用')
Beispiel #12
0
    def __init__(self,
                 username,
                 password,
                 model="SIM",
                 broker_name="QAPaperTrading",
                 trade_host='127.0.0.1'):
        """Initial
        QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类


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

        使用 model = SIM/ REAL来切换


        """
        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
        self.db = pymongo.MongoClient(trade_host).QAREALTIME

        self.pub_host = ""
        self.trade_host = ""
        self.last_updatetime = ""
        self.status = 200
        self.trading_day = ""

        self.pre_balance = 0

        self.static_balance = 0

        self.deposit = 0  # 入金
        self.withdraw = 0  # 出金
        self.withdrawQuota = 0  # 可取金额
        self.close_profit = 0
        self.event_id = 0
        self.money = 0
        # QIFI 协议
        self.transfers = {}

        self.banks = {}

        self.frozen = {}

        self.events = {}
        self.positions = {}
        self.trades = {}
        self.orders = {}
Beispiel #13
0
 def get(self):
     mp = MARKET_PRESET()
     self.write({'result': QA_util_to_json_from_pandas(mp.pdtable.T)})
Beispiel #14
0
class QA_Performance():
    """
    QA_Performance是一个绩效分析插件

    需要加载一个account/portfolio类进来:
    需要有
    code,start_date,end_date,daily_cash,daily_hold


    QAPERFORMANCE 的评估字段

    1. 对于多头开仓/ 空头开仓的分析
    2. 总盈利(对于每个单笔而言)
    3. 总亏损(对于每个单笔而言)
    4. 总盈利/总亏损
    5. 交易手数
    6. 盈利比例
    7. 盈利手数
    8. 亏损手数
    9. 持平手数
    10. 平均利润
    11. 平均盈利
    12. 平均亏损
    13. 平均盈利/平均亏损
    14. 最大盈利(单笔)
    15. 最大亏损(单笔)
    16. 最大盈利/总盈利
    17. 最大亏损/总亏损
    18. 净利润/最大亏损
    19. 最大连续盈利手数
    20. 最大连续亏损手数
    21. 平均持仓周期
    22. 平均盈利周期
    23. 平均亏损周期
    24. 平均持平周期
    25. 最大使用资金
    26. 最大持仓手数
    27. 交易成本合计
    28. 收益率
    29. 年化收益率
    30. 有效收益率
    31. 月度平均盈利
    32. 收益曲线斜率
    33. 收益曲线截距
    34. 收益曲线R2值
    35. 夏普比例
    36. 总交易时间
    37. 总持仓时间
    38. 持仓时间比例
    39. 最大空仓时间
    40. 持仓周期
    41. 资产最大升水
    42. 发生时间
    43. 最大升水/前期低点
    44. 单日最大资产回撤比率
    45. 最大资产回撤值
    46. 最大资产回撤发生时间
    47. 回撤值/前期高点
    48. 净利润/回撤值


    """

    def __init__(self, target):

        self.target = target
        self._style_title = [
            'beta',
            'momentum',
            'size',
            'earning_yield',
            'volatility',
            'growth',
            'value',
            'leverage',
            'liquidity',
            'reversal'
        ]
        self.market_preset = MARKET_PRESET()
        self.pnl = self.pnl_fifo

    def __repr__(self):
        return '< QA_PERFORMANCE ANYLYSIS PLUGIN >'

    def set_pnl(self, model='fifo'):
        if model == 'fifo':
            self.pnl = self.pnl_fifo
        elif model == 'lifo':
            self.pnl = self.pnl_lifo

    def base_message(self, pnl):
        return {'total_profit': round(self.total_profit(pnl), 2),  # 总盈利(对于每个单笔而言)
                'total_loss': round(self.total_loss(pnl), 2),  # 总亏损(对于每个单笔而言)
                'total_pnl': round(self.total_pnl(pnl), 2),  # 总盈利/总亏损
                'trading_amounts': round(self.trading_amounts(pnl), 2),  # 交易手数
                'profit_amounts': round(self.profit_amounts(pnl), 2),  # 盈利手数
                'loss_amounts': round(self.loss_amounts(pnl), 2),  # 亏损手数
                'even_amounts': round(self.even_amounts(pnl), 2),  # 持平手数
                'profit_precentage': round(self.profit_precentage(pnl), 2),
                'loss_precentage': round(self.loss_precentage(pnl), 2),
                'even_precentage': round(self.even_precentage(pnl), 2),
                'average_profit': round(self.average_profit(pnl), 2),
                'average_loss': round(self.average_loss(pnl), 2),
                'average_pnl': round(self.average_pnl(pnl), 2),
                'max_profit': round(self.max_profit(pnl), 2),
                'max_loss': round(self.max_loss(pnl), 2),
                'max_pnl': round(self.max_pnl(pnl), 2),
                'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(pnl), 2),
                'continue_profit_amount': round(self.continue_profit_amount(pnl), 2),
                'continue_loss_amount': round(self.continue_loss_amount(pnl), 2),
                'average_holdgap': self.average_holdgap(pnl),
                'average_profitholdgap': self.average_profitholdgap(pnl),
                'average_losssholdgap': self.average_losssholdgap(pnl)}

    @property
    def message(self):
        """[summary]
            2. 
            3. 
            4. 
            5. 
            6. 
            7. 盈利手数
            8. 亏损手数
            9. 持平手数
            10. 平均利润
            11. 平均盈利
            12. 平均亏损
            13. 平均盈利/平均亏损
            14. 最大盈利(单笔)
            15. 最大亏损(单笔)
            16. 最大盈利/总盈利
            17. 最大亏损/总亏损
            18. 净利润/最大亏损
            19. 最大连续盈利手数
            20. 最大连续亏损手数
            21. 平均持仓周期
            22. 平均盈利周期
            23. 平均亏损周期
            24. 平均持平周期
            25. 最大使用资金
            26. 最大持仓手数
            27. 交易成本合计
            28. 收益率
            29. 年化收益率
            30. 有效收益率
            31. 月度平均盈利
            32. 收益曲线斜率
            33. 收益曲线截距
            34. 收益曲线R2值
            35. 夏普比例
            36. 总交易时间
            37. 总持仓时间
            38. 持仓时间比例
            39. 最大空仓时间
            40. 持仓周期
            41. 资产最大升水
            42. 发生时间
            43. 最大升水/前期低点
            44. 单日最大资产回撤比率
            45. 最大资产回撤值
            46. 最大资产回撤发生时间
            47. 回撤值/前期高点
            48. 净利润/回撤值
        Returns:
            [type] -- [description]
        """

        return {
            # 总盈利(对于每个单笔而言)
            'total_profit': round(self.total_profit(self.pnl), 2),
            'total_loss': round(self.total_loss(self.pnl), 2),  # 总亏损(对于每个单笔而言)
            'total_pnl': round(self.total_pnl(self.pnl), 2),  # 总盈利/总亏损
            # 交易手数
            'trading_amounts': round(self.trading_amounts(self.pnl), 2),
            'profit_amounts': round(self.profit_amounts(self.pnl), 2),  # 盈利手数
            'loss_amounts': round(self.loss_amounts(self.pnl), 2),  # 亏损手数
            'even_amounts': round(self.even_amounts(self.pnl), 2),  # 持平手数
            'profit_precentage': round(self.profit_precentage(self.pnl), 2),
            'loss_precentage': round(self.loss_precentage(self.pnl), 2),
            'even_precentage': round(self.even_precentage(self.pnl), 2),
            'average_profit': round(self.average_profit(self.pnl), 2),
            'average_loss': round(self.average_loss(self.pnl), 2),
            'average_pnl': round(self.average_pnl(self.pnl), 2),
            'max_profit': round(self.max_profit(self.pnl), 2),
            'max_loss': round(self.max_loss(self.pnl), 2),
            'max_pnl': round(self.max_pnl(self.pnl), 2),
            'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(self.pnl), 2),
            'continue_profit_amount': round(self.continue_profit_amount(self.pnl), 2),
            'continue_loss_amount': round(self.continue_loss_amount(self.pnl), 2),
            'average_holdgap': self.average_holdgap(self.pnl),
            'average_profitholdgap': self.average_profitholdgap(self.pnl),
            'average_losssholdgap': self.average_losssholdgap(self.pnl),
            'buyopen': self.base_message(self.pnl_buyopen),
            'sellopen': self.base_message(self.pnl_sellopen)
        }

    @property
    def prefer(self):
        pass

    @property
    def style(self):
        """风格分析
        """
        pass

    @property
    def pnl_lifo(self):
        """
        使用后进先出法配对成交记录
        """
        X = dict(
            zip(
                self.target.code,
                [LifoQueue() for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:
                if X[data.code].qsize() == 0:
                    X[data.code].put((data.datetime, data.amount, data.price))
                    break
                else:
                    l = X[data.code].get()
                    if (l[1] * data.amount) < 0:
                        # 原有多仓/ 平仓 或者原有空仓/平仓

                        if abs(l[1]) > abs(data.amount):
                            temp = (l[0], l[1] + data.amount, l[2])
                            X[data.code].put_nowait(temp)
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price

                                    ]
                                )
                                break

                        elif abs(l[1]) < abs(data.amount):
                            data.amount = data.amount + l[1]

                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        l[1],
                                        data.price,
                                        l[2]
                                    ]
                                )
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        l[1],
                                        l[2],
                                        data.price
                                    ]
                                )
                        else:
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price
                                    ]
                                )
                                break

                    else:
                        X[data.code].put_nowait(l)
                        X[data.code].put_nowait(
                            (data.datetime,
                             data.amount,
                             data.price)
                        )
                        break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title).set_index('code')
        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=(pnl.sell_date -
                        pnl.buy_date) > datetime.timedelta(days=0)
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str))
        return pnl.set_index('code')

    @property
    def pnl_buyopen(self):
        return self.pnl[self.pnl.if_buyopen]

    @property
    def pnl_sellopen(self):
        return self.pnl[~self.pnl.if_buyopen]

    @property
    def pnl_fifo(self):
        X = dict(
            zip(
                self.target.code,
                [deque() for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:
                if len(X[data.code]) == 0:
                    X[data.code].append(
                        (data.datetime,
                         data.amount,
                         data.price)
                    )
                    break
                else:
                    l = X[data.code].popleft()
                    if (l[1] * data.amount) < 0:
                        # 原有多仓/ 平仓 或者原有空仓/平仓

                        if abs(l[1]) > abs(data.amount):
                            temp = (l[0], l[1] + data.amount, l[2])
                            X[data.code].appendleft(temp)
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price

                                    ]
                                )
                                break

                        elif abs(l[1]) < abs(data.amount):
                            data.amount = data.amount + l[1]

                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        l[1],
                                        data.price,
                                        l[2]
                                    ]
                                )
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        l[1],
                                        l[2],
                                        data.price

                                    ]
                                )
                        else:
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price


                                    ]
                                )
                                break

                    else:
                        X[data.code].appendleft(l)
                        X[data.code].appendleft(
                            (data.datetime,
                             data.amount,
                             data.price)
                        )
                        break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title)

        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=(pnl.sell_date -
                        pnl.buy_date) > datetime.timedelta(days=0)
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str))
        return pnl.set_index('code')

    def plot_pnlratio(self):
        """
        画出pnl比率散点图
        """

        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_ratio)
        plt.gcf().autofmt_xdate()
        return plt

    def plot_pnlmoney(self):
        """
        画出pnl盈亏额散点图
        """
        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_money)
        plt.gcf().autofmt_xdate()
        return plt

    def abnormal_active(self):
        """
        账户的成交发生异常成交记录的分析
        """
        pass

    def brinson(self):
        """Brinson Model analysis
        """
        pass

    def hold(self):
        """持仓分析
        """
        pass

    def win_rate(self):
        """胜率

        胜率
        盈利次数/总次数
        """
        data = self.pnl
        try:
            return round(len(data.query('pnl_money>0')) / len(data), 2)
        except ZeroDivisionError:
            return 0

    @property
    def accumulate_return(self):
        """
        returns a pd-Dataframe format accumulate return for different periods
        """
        pass

    def save(self):
        """save the performance analysis result to database
        """
        pass

    def profit_pnl(self, pnl):
        return pnl.query('pnl_money>0')

    def loss_pnl(self, pnl):
        return pnl.query('pnl_money<0')

    def even_pnl(self, pnl):
        return pnl.query('pnl_money==0')

    def total_profit(self, pnl):
        if len(self.profit_pnl(pnl))>0:
            return self.profit_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_loss(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            return self.loss_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_pnl(self, pnl):
        try:
            return abs(self.total_profit(pnl) / self.total_loss(pnl))
        except ZeroDivisionError:
            return 0

    def trading_amounts(self, pnl):
        return len(pnl)

    def profit_amounts(self, pnl):
        return len(self.profit_pnl(pnl))

    def loss_amounts(self, pnl):
        return len(self.loss_pnl(pnl))

    def even_amounts(self, pnl):
        return len(self.even_pnl(pnl))

    def profit_precentage(self, pnl):
        try:
            return self.profit_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def loss_precentage(self, pnl):
        try:
            return self.loss_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def even_precentage(self, pnl):
        try:
            return self.even_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def average_loss(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            return self.loss_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_profit(self, pnl):
        if len(self.profit_pnl(pnl))>0:
            return self.profit_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_pnl(self, pnl):
        if len(self.loss_pnl(pnl))>0 and len(self.profit_pnl(pnl))>0:
            try:
                return abs(self.average_profit(pnl) / self.average_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def max_profit(self, pnl):
        if len(self.profit_pnl(pnl))>0:
            return self.profit_pnl(pnl).pnl_money.max()
        else:
            return 0

    def max_loss(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            return self.loss_pnl(pnl).pnl_money.min()
        else:
            return 0

    def max_pnl(self, pnl):
        try:
            return abs(self.max_profit(pnl) / self.max_loss(pnl))
        except ZeroDivisionError:
            return 0

    def netprofio_maxloss_ratio(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            try:
                return abs(pnl.pnl_money.sum() / self.max_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def continue_profit_amount(self, pnl):
        w = []
        w1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                w1 += 1
            elif item < 0:
                w.append(w1)
                w1 = 0
        if len(w) == 0:
            return 0
        else:
            return max(w)

    def continue_loss_amount(self, pnl):
        l = []
        l1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                l1 += 1
            elif item < 0:
                l.append(l1)
                l1 = 0
        if len(l) == 0:
            return 0
        else:
            return max(l)

    def average_holdgap(self, pnl):
        if len(pnl.hold_gap)>0:
            return str(pnl.hold_gap.mean())
        else:
            return 'no trade'

    def average_profitholdgap(self, pnl):
        if len(self.profit_pnl(pnl).hold_gap)>0:
            return str(self.profit_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_losssholdgap(self, pnl):
        if len(self.loss_pnl(pnl).hold_gap)>0:
            return str(self.loss_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_evenholdgap(self, pnl):
        if len(self.even_pnl(pnl).hold_gap)>0:
            return self.even_pnl(pnl).hold_gap.mean()
        else:
            return 'no trade'

    @property
    def max_cashused(self):
        return self.target.init_cash - min(self.target.cash)

    @property
    def total_taxfee(self):
        return self.target.history_table_min.commission.sum(
        ) + self.target.history_table_min.tax.sum()
Beispiel #15
0
    def __init__(self,
                 code='000001',
                 account_cookie='quantaxis',
                 portfolio_cookie='portfolio',
                 username='******',
                 user_cookie = '',
                 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.user_cookie = user_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
Beispiel #16
0
class QA_Performance():
    """
    QA_Performance是一个绩效分析插件

    需要加载一个account/portfolio类进来:
    需要有
    code,start_date,end_date,daily_cash,daily_hold


    QAPERFORMANCE 的评估字段

    1. 对于多头开仓/ 空头开仓的分析
    2. 总盈利(对于每个单笔而言)
    3. 总亏损(对于每个单笔而言)
    4. 总盈利/总亏损
    5. 交易手数
    6. 盈利比例
    7. 盈利手数
    8. 亏损手数
    9. 持平手数
    10. 平均利润
    11. 平均盈利
    12. 平均亏损
    13. 平均盈利/平均亏损
    14. 最大盈利(单笔)
    15. 最大亏损(单笔)
    16. 最大盈利/总盈利
    17. 最大亏损/总亏损
    18. 净利润/最大亏损
    19. 最大连续盈利手数
    20. 最大连续亏损手数
    21. 平均持仓周期
    22. 平均盈利周期
    23. 平均亏损周期
    24. 平均持平周期
    25. 最大使用资金
    26. 最大持仓手数
    27. 交易成本合计
    28. 收益率
    29. 年化收益率
    30. 有效收益率
    31. 月度平均盈利
    32. 收益曲线斜率
    33. 收益曲线截距
    34. 收益曲线R2值
    35. 夏普比例
    36. 总交易时间
    37. 总持仓时间
    38. 持仓时间比例
    39. 最大空仓时间
    40. 持仓周期
    41. 资产最大升水
    42. 发生时间
    43. 最大升水/前期低点
    44. 单日最大资产回撤比率
    45. 最大资产回撤值
    46. 最大资产回撤发生时间
    47. 回撤值/前期高点
    48. 净利润/回撤值


    """

    def __init__(self, target):

        self.target = target
        self._style_title = [
            'beta',
            'momentum',
            'size',
            'earning_yield',
            'volatility',
            'growth',
            'value',
            'leverage',
            'liquidity',
            'reversal'
        ]
        self.market_preset = MARKET_PRESET()
        self.pnl = self.pnl_fifo

    def __repr__(self):
        return '< QA_PERFORMANCE ANYLYSIS PLUGIN >'

    def set_pnl(self, model='fifo'):
        if model == 'fifo':
            self.pnl = self.pnl_fifo
        elif model == 'lifo':
            self.pnl = self.pnl_lifo

    def base_message(self, pnl):
        return {'total_profit': round(self.total_profit(pnl), 2),  # 总盈利(对于每个单笔而言)
                'total_loss': round(self.total_loss(pnl), 2),  # 总亏损(对于每个单笔而言)
                'total_pnl': round(self.total_pnl(pnl), 2),  # 总盈利/总亏损
                'trading_amounts': round(self.trading_amounts(pnl), 2),  # 交易手数
                'profit_amounts': round(self.profit_amounts(pnl), 2),  # 盈利手数
                'loss_amounts': round(self.loss_amounts(pnl), 2),  # 亏损手数
                'even_amounts': round(self.even_amounts(pnl), 2),  # 持平手数
                'profit_precentage': round(self.profit_precentage(pnl), 2),
                'loss_precentage': round(self.loss_precentage(pnl), 2),
                'even_precentage': round(self.even_precentage(pnl), 2),
                'average_profit': round(self.average_profit(pnl), 2),
                'average_loss': round(self.average_loss(pnl), 2),
                'average_pnl': round(self.average_pnl(pnl), 2),
                'max_profit': round(self.max_profit(pnl), 2),
                'max_loss': round(self.max_loss(pnl), 2),
                'max_pnl': round(self.max_pnl(pnl), 2),
                'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(pnl), 2),
                'continue_profit_amount': round(self.continue_profit_amount(pnl), 2),
                'continue_loss_amount': round(self.continue_loss_amount(pnl), 2),
                'average_holdgap': self.average_holdgap(pnl),
                'average_profitholdgap': self.average_profitholdgap(pnl),
                'average_losssholdgap': self.average_losssholdgap(pnl)}

    @property
    def message(self):
        """[summary]
            2. 
            3. 
            4. 
            5. 
            6. 
            7. 盈利手数
            8. 亏损手数
            9. 持平手数
            10. 平均利润
            11. 平均盈利
            12. 平均亏损
            13. 平均盈利/平均亏损
            14. 最大盈利(单笔)
            15. 最大亏损(单笔)
            16. 最大盈利/总盈利
            17. 最大亏损/总亏损
            18. 净利润/最大亏损
            19. 最大连续盈利手数
            20. 最大连续亏损手数
            21. 平均持仓周期
            22. 平均盈利周期
            23. 平均亏损周期
            24. 平均持平周期
            25. 最大使用资金
            26. 最大持仓手数
            27. 交易成本合计
            28. 收益率
            29. 年化收益率
            30. 有效收益率
            31. 月度平均盈利
            32. 收益曲线斜率
            33. 收益曲线截距
            34. 收益曲线R2值
            35. 夏普比例
            36. 总交易时间
            37. 总持仓时间
            38. 持仓时间比例
            39. 最大空仓时间
            40. 持仓周期
            41. 资产最大升水
            42. 发生时间
            43. 最大升水/前期低点
            44. 单日最大资产回撤比率
            45. 最大资产回撤值
            46. 最大资产回撤发生时间
            47. 回撤值/前期高点
            48. 净利润/回撤值
        Returns:
            [type] -- [description]
        """

        return {
            # 总盈利(对于每个单笔而言)
            'total_profit': round(self.total_profit(self.pnl), 2),
            'total_loss': round(self.total_loss(self.pnl), 2),  # 总亏损(对于每个单笔而言)
            'total_pnl': round(self.total_pnl(self.pnl), 2),  # 总盈利/总亏损
            # 交易手数
            'trading_amounts': round(self.trading_amounts(self.pnl), 2),
            'profit_amounts': round(self.profit_amounts(self.pnl), 2),  # 盈利手数
            'loss_amounts': round(self.loss_amounts(self.pnl), 2),  # 亏损手数
            'even_amounts': round(self.even_amounts(self.pnl), 2),  # 持平手数
            'profit_precentage': round(self.profit_precentage(self.pnl), 2),
            'loss_precentage': round(self.loss_precentage(self.pnl), 2),
            'even_precentage': round(self.even_precentage(self.pnl), 2),
            'average_profit': round(self.average_profit(self.pnl), 2),
            'average_loss': round(self.average_loss(self.pnl), 2),
            'average_pnl': round(self.average_pnl(self.pnl), 2),
            'max_profit': round(self.max_profit(self.pnl), 2),
            'max_loss': round(self.max_loss(self.pnl), 2),
            'max_pnl': round(self.max_pnl(self.pnl), 2),
            'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(self.pnl), 2),
            'continue_profit_amount': round(self.continue_profit_amount(self.pnl), 2),
            'continue_loss_amount': round(self.continue_loss_amount(self.pnl), 2),
            'average_holdgap': self.average_holdgap(self.pnl),
            'average_profitholdgap': self.average_profitholdgap(self.pnl),
            'average_losssholdgap': self.average_losssholdgap(self.pnl),
            'buyopen': self.base_message(self.pnl_buyopen),
            'sellopen': self.base_message(self.pnl_sellopen)
        }

    @property
    def prefer(self):
        pass

    @property
    def style(self):
        """风格分析
        """
        pass

    @property
    def pnl_lifo(self):
        """
        使用后进先出法配对成交记录
        """
        X = dict(
            zip(
                self.target.code,
                [{'buy': LifoQueue(), 'sell': LifoQueue()}
                 for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:

                if data.direction in[1, 2, -2]:
                    if data.direction in [1, 2]:
                        X[data.code]['buy'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    elif data.direction in [-2]:
                        X[data.code]['sell'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    break
                elif data.direction in[-1, 3, -3]:

                    rawoffset = 'buy' if data.direction in [-1, -3] else 'sell'

                    l = X[data.code][rawoffset].get()
                    if abs(l[1]) > abs(data.amount):
                        """
                        if raw> new_close:
                        """
                        temp = (l[0], l[1] + data.amount, l[2])
                        X[data.code][rawoffset].put_nowait(temp)
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

                    elif abs(l[1]) < abs(data.amount):
                        data.amount = data.amount + l[1]

                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    l[1],
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    l[1],
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                    else:
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price',
            'rawdirection'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title)

        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=pnl.rawdirection == 'buy'
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.sell_date.map(str)
        )
        return pnl.set_index('code')

    @property
    def pnl_buyopen(self):
        return self.pnl[self.pnl.if_buyopen]

    @property
    def pnl_sellopen(self):
        return self.pnl[~self.pnl.if_buyopen]

    @property
    def pnl_fifo(self):
        X = dict(
            zip(
                self.target.code,
                [{'buy': deque(), 'sell': deque()}
                 for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:

                if data.direction in[1, 2, -2]:
                    if data.direction in [1, 2]:
                        X[data.code]['buy'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    elif data.direction in [-2]:
                        X[data.code]['sell'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    break
                elif data.direction in[-1, 3, -3]:

                    rawoffset = 'buy' if data.direction in [-1, -3] else 'sell'

                    l = X[data.code][rawoffset].popleft()
                    if abs(l[1]) > abs(data.amount):
                        """
                        if raw> new_close:
                        """
                        temp = (l[0], l[1] + data.amount, l[2])
                        X[data.code][rawoffset].appendleft(temp)
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

                    elif abs(l[1]) < abs(data.amount):
                        data.amount = data.amount + l[1]

                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    l[1],
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    l[1],
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                    else:
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price',
            'rawdirection'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title)

        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=pnl.rawdirection == 'buy'
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.sell_date.map(str)
        )
        return pnl.set_index('code')

    def plot_pnlratio(self):
        """
        画出pnl比率散点图
        """

        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_ratio)
        plt.gcf().autofmt_xdate()
        return plt

    def plot_pnlmoney(self):
        """
        画出pnl盈亏额散点图
        """
        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_money)
        plt.gcf().autofmt_xdate()
        return plt

    def abnormal_active(self):
        """
        账户的成交发生异常成交记录的分析
        """
        pass

    def brinson(self):
        """Brinson Model analysis
        """
        pass

    def hold(self):
        """持仓分析
        """
        pass

    def win_rate(self):
        """胜率

        胜率
        盈利次数/总次数
        """
        data = self.pnl
        try:
            return round(len(data.query('pnl_money>0')) / len(data), 2)
        except ZeroDivisionError:
            return 0

    @property
    def accumulate_return(self):
        """
        returns a pd-Dataframe format accumulate return for different periods
        """
        pass

    def save(self):
        """save the performance analysis result to database
        """
        pass

    def profit_pnl(self, pnl):
        return pnl.query('pnl_money>0')

    def loss_pnl(self, pnl):
        return pnl.query('pnl_money<0')

    def even_pnl(self, pnl):
        return pnl.query('pnl_money==0')

    def total_profit(self, pnl):
        if len(self.profit_pnl(pnl)) > 0:
            return self.profit_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_loss(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            return self.loss_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_pnl(self, pnl):
        try:
            return abs(self.total_profit(pnl) / self.total_loss(pnl))
        except ZeroDivisionError:
            return 0

    def trading_amounts(self, pnl):
        return len(pnl)

    def profit_amounts(self, pnl):
        return len(self.profit_pnl(pnl))

    def loss_amounts(self, pnl):
        return len(self.loss_pnl(pnl))

    def even_amounts(self, pnl):
        return len(self.even_pnl(pnl))

    def profit_precentage(self, pnl):
        try:
            return self.profit_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def loss_precentage(self, pnl):
        try:
            return self.loss_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def even_precentage(self, pnl):
        try:
            return self.even_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def average_loss(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            return self.loss_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_profit(self, pnl):
        if len(self.profit_pnl(pnl)) > 0:
            return self.profit_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_pnl(self, pnl):
        if len(self.loss_pnl(pnl)) > 0 and len(self.profit_pnl(pnl)) > 0:
            try:
                return abs(self.average_profit(pnl) / self.average_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def max_profit(self, pnl):
        if len(self.profit_pnl(pnl)) > 0:
            return self.profit_pnl(pnl).pnl_money.max()
        else:
            return 0

    def max_loss(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            return self.loss_pnl(pnl).pnl_money.min()
        else:
            return 0

    def max_pnl(self, pnl):
        try:
            return abs(self.max_profit(pnl) / self.max_loss(pnl))
        except ZeroDivisionError:
            return 0

    def netprofio_maxloss_ratio(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            try:
                return abs(pnl.pnl_money.sum() / self.max_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def continue_profit_amount(self, pnl):
        w = []
        w1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                w1 += 1
            elif item < 0:
                w.append(w1)
                w1 = 0
        if len(w) == 0:
            return 0
        else:
            return max(w)

    def continue_loss_amount(self, pnl):
        l = []
        l1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                l1 += 1
            elif item < 0:
                l.append(l1)
                l1 = 0
        if len(l) == 0:
            return 0
        else:
            return max(l)

    def average_holdgap(self, pnl):
        if len(pnl.hold_gap) > 0:
            return str(pnl.hold_gap.mean())
        else:
            return 'no trade'

    def average_profitholdgap(self, pnl):
        if len(self.profit_pnl(pnl).hold_gap) > 0:
            return str(self.profit_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_losssholdgap(self, pnl):
        if len(self.loss_pnl(pnl).hold_gap) > 0:
            return str(self.loss_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_evenholdgap(self, pnl):
        if len(self.even_pnl(pnl).hold_gap) > 0:
            return self.even_pnl(pnl).hold_gap.mean()
        else:
            return 'no trade'

    @property
    def max_cashused(self):
        return self.target.init_cash - min(self.target.cash)

    @property
    def total_taxfee(self):
        return self.target.history_table_min.commission.sum(
        ) + self.target.history_table_min.tax.sum()
Beispiel #17
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
Beispiel #18
0
    def __init__(
            self,
            code='000001',
            account_cookie='quantaxis',
            moneypreset=100000,  # 初始分配资金
            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,
            market_type=MARKET_TYPE.STOCK_CN,
            exchange_id=EXCHANGE_ID.SZSE,
            name=None,
            *args,
            **kwargs):

        self.code = code
        self.account_cookie = account_cookie
        self.time = ''
        self.market_preset = MARKET_PRESET().get_code(self.code)
        self.position_id = str(uuid.uuid4())
        self.moneypreset = moneypreset
        self.moneypresetLeft = self.moneypreset
        """{'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
        self.market_type = market_type
        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.trades = {}
Beispiel #19
0
    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 = {}
Beispiel #20
0
    def __init__(
            self,
            price=None,
            date=None,
            datetime=None,
            sending_time=None,
            trade_time=False,
            amount=0,
            market_type=None,
            frequence=None,
            towards=None,
            code=None,
            user_cookie=None,
            account_cookie=None,
            strategy=None,
            order_model=None,
            money=None,
            amount_model=AMOUNT_MODEL.BY_AMOUNT,
            broker=None,
            order_id=None,
            trade_id=False,
            _status=ORDER_STATUS.NEW,
            callback=False,
            commission_coeff=0.00025,
            tax_coeff=0.001,
            exchange_id=None,
            position_id=None,
            *args,
            **kwargs
    ):
        '''




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


        :param args: type tuple
        :param kwargs: type dict

        # 2018-08-12 把order变成一个状态机>
        # 以前的order只是一个信息承载的工具,现在需要让他具备状态的方法

        NEW = 100
        SUCCESS_ALL = 200
        SUCCESS_PART = 203 # success_part 是部分成交 一个中间状态 剩余的订单还在委托队列中
        QUEUED = 300  # queued 用于表示在order_queue中 实际表达的意思是订单存活 待成交
        CANCEL = 400
        CANCEL_PART = 402 # cancel_part是部分撤单(及 下单后成交了一部分 剩余的被撤单 这是一个最终状态)
        SETTLED = 500
        FAILED = 600
        '''

        self.price = price
        self.datetime = None

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

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

        self.trade_price = 0                                       # 成交均价
        self.broker = broker
        self.callback = callback                                   # 委托成功的callback
        self.money = money                                         # 委托需要的金钱
        self.reason = None                                         # 原因列表
        self.exchange_id = exchange_id
        self.time_condition = 'GFD'                                # 当日有效
        self._status = _status

        self.position_id = position_id
        # 增加订单对于多账户以及多级别账户的支持 2018/11/12
        self.mainacc_id = None if 'mainacc_id' not in kwargs.keys(
        ) else kwargs['mainacc_id']
        self.subacc_id = None if 'subacc_id' not in kwargs.keys(
        ) else kwargs['subacc_id']
        self.direction = 'BUY' if self.towards in [
            ORDER_DIRECTION.BUY,
            ORDER_DIRECTION.BUY_OPEN,
            ORDER_DIRECTION.BUY_CLOSE
        ] else 'SELL'
        self.offset = 'OPEN' if self.towards in [
            ORDER_DIRECTION.BUY,
            ORDER_DIRECTION.BUY_OPEN,
            ORDER_DIRECTION.SELL_OPEN
        ] else 'CLOSE'
Beispiel #21
0
    def __init__(self,
                 code='000001',
                 user_id='quantaxis',
                 account_cookie='quantaxis',
                 portfolio_cookie='quantaxis',
                 user_cookie='quantaxis',
                 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,


                 market_type=MARKET_TYPE.STOCK_CN,
                 exchange_id=EXCHANGE_ID.SZSE,
                 name=None,

                 ):

        self.code = code
        self.user_id = user_id

        self.market_preset = MARKET_PRESET().get_code(self.code)

        """{'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.name = name
        self.market_type = market_type
        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 = position_price_long
        self.position_price_short = position_price_short

        self.open_cost_long = open_cost_long
        self.open_cost_short = open_cost_short
        self.position_cost_long = position_cost_long
        self.position_cost_short = position_cost_short

        self.last_price = 0