# -*- coding: utf-8 -*-

"""
    用作TraderSpi的Stub
"""

import logging
from datetime import datetime

from ..ctp_api import UserApiStruct as UStruct
from ..ctp_api import UserApiType as UType

from ..common.base import BaseObject,LONG,SHORT,TICK
from ..common.base import TEST_PATH as LOG_PATH
from ..common.contract_type import CM_ALL_TEST as call,ContractInfo


class TraderApiStub(object):
    logger = logging.getLogger('ctp.TraderApiStub')

    @staticmethod
    def CreateTraderApi(account,available = 1000000):
        return TraderApiStub(account,available)

    def __init__(self,account,available):
        self._account = account
        self._trading_day = datetime.now().strftime("%Y%m%d")
        self._exchange = ExchangeStub(self._trading_day,available)
        self._exchange_id = "TAStub_Exchange"
        self._investor_id = "TAStub_Investor"
        self._broker_id = "TAStub_BrokerID"
        self._front_id = 0
        self._session_id = 999999
        self._defaultReqInfo = BaseObject(BrokerID = self._broker_id,InvestorID = self._investor_id)
        self._suceessRspInfo = UStruct.RspInfo(0,"No errors")
        self._failureRspInfo = UStruct.RspInfo(1,"Error")
        self._init_order_ref = 10000000
        self._current_order_sys_id = 20000000
        #self._contracts = dict([(contract.name,contract) for contract in call.current_contracts()])
        self._contracts = {} #name=>contracts的映射,在QueryInstrument中填充
        self._ctick = TICK(instrument='C_STUB',date=self._trading_day)
        self._last_tick_map = {}    #instrument_id => last_tick
        self._wlist = {}     #contract_name ==> pOrder的列表,等待成交
        self._cancelled = 0  #仅用于测试
        self._cancel_after_traded = 0
        self._orders = []

    @property
    def contracts(self):
        return self._contracts

    @property
    def cancelled(self):
        return self._cancelled

    @property
    def last_tick_map(self):
        return self._last_tick_map

    @property
    def cancel_after_traded(self):
        return self._cancel_after_traded

    @property
    def exchange(self):
        return self._exchange

    @property
    def available(self):
        return self._exchange._available

    def RegisterSpi(self, pSpi):
        ''' 注册回调接口
            @param pSpi 派生自回调接口类的实例
        '''
        self.spi = pSpi
        pSpi.register_api(self)
        return True

    def inc_order_sys_id(self):
        self._current_order_sys_id += 1
        return self._current_order_sys_id

    def _add_contract(self,contract):
        """
            添加可被测试用到的contract. 仅用于测试,不用于模拟
        """
        self._contracts[contract.name] = contract    #不判断直接赋值

    def new_tick(self,tick):
        """
            设置所需用到的成交价格
        """
        self._ctick = tick
        self._last_tick_map[tick.instrument] = tick
        #print(tick.instrument,tick.time)
        self._check_trade(tick)     #判断是否有新的成交

    def ReqParkedOrderAction(self, pParkedOrderAction, nRequestID):
        '''预埋撤单录入请求'''
        #未实现
        pass

    def ReqQueryBankAccountMoneyByFuture(self, pReqQueryAccount, nRequestID):
        '''期货发起查询银行余额请求'''
        #未实现
        pass

    def ReqQryTrade(self, pQryTrade, nRequestID):
        '''请求查询成交'''
        #未实现
        pass

    def ReqParkedOrderInsert(self, pParkedOrder, nRequestID):
        '''预埋单录入请求'''
        #未实现
        pass

    def ReqRemoveParkedOrder(self, pRemoveParkedOrder, nRequestID):
        '''请求删除预埋单'''
        #未实现
        pass

    def ReqQryParkedOrder(self, pQryParkedOrder, nRequestID):
        '''请求查询预埋单'''
        #未实现
        pass

    def ReqAuthenticate(self, pReqAuthenticateField, nRequestID):
        '''客户端认证请求'''
        #未实现
        pass

    def ReqQryTransferBank(self, pQryTransferBank, nRequestID):
        '''请求查询转帐银行'''
        #未实现
        pass

    def RegisterFront(self, pszFrontAddress):
        '''注册前置机网络地址
@param pszFrontAddress：前置机网络地址。
@remark 网络地址的格式为：“protocol://ipaddress:port”，如：”tcp://127.0.0.1:17001”。
@remark “tcp”代表传输协议，“127.0.0.1”代表服务器地址。”17001”代表服务器端口号。'''
        #Stub不需要注册
        self.logger.info("TAStub-RF:%s" % (pszFrontAddress,))
        pass

    def ReqQryInvestorProductGroupMargin(self, pQryInvestorProductGroupMargin, nRequestID):
        '''请求查询投资者品种/跨品种保证金'''
        #未实现
        pass

    def SubscribePrivateTopic(self, nResumeType):
        '''订阅私有流。
@param nResumeType 私有流重传方式
        THOST_TERT_RESTART:从本交易日开始重传
        THOST_TERT_RESUME:从上次收到的续传
        THOST_TERT_QUICK:只传送登录后私有流的内容
@remark 该方法要在Init方法前调用。若不调用则不会收到私有流的数据。'''
        #Stub不需要实现
        pass

    def ReqFromBankToFutureByFuture(self, pReqTransfer, nRequestID):
        '''期货发起银行资金转期货请求'''
        #未实现
        pass


    def ReqQryEWarrantOffset(self, pQryEWarrantOffset, nRequestID):
        '''请求查询仓单折抵信息'''
        #未实现
        pass

    def ReqRemoveParkedOrderAction(self, pRemoveParkedOrderAction, nRequestID):
        '''请求删除预埋撤单'''
        #未实现
        pass

    def ReqQryParkedOrderAction(self, pQryParkedOrderAction, nRequestID):
        '''请求查询预埋撤单'''
        #未实现
        pass

    def ReqQryExchangeMarginRate(self, pQryExchangeMarginRate, nRequestID):
        '''请求查询交易所保证金率'''
        #未实现
        pass

    def ReqUserLogout(self, pUserLogout, nRequestID):
        '''登出请求'''
        #Stub不需要实现
        pass

    def ReqQryExchangeMarginRateAdjust(self, pQryExchangeMarginRateAdjust, nRequestID):
        '''请求查询交易所调整保证金率'''
        #未实现
        pass

    def ReqSettlementInfoConfirm(self, pSettlementInfoConfirm, nRequestID):
        '''投资者结算结果确认'''
        self.spi.OnRspSettlementInfoConfirm(pSettlementInfoConfirm, self._suceessRspInfo, nRequestID, True)

    def Join(self, ):
        '''等待接口线程结束运行
@return 线程退出代码'''
        #Stub不需要实现
        pass

    def ReqQryDepthMarketData(self, pQryDepthMarketData, nRequestID):
        '''请求查询行情'''
        if pQryDepthMarketData.InstrumentID in self._last_tick_map:
            cp = self._last_tick_map[pQryDepthMarketData.InstrumentID].price
            uprice = cp * 1.05
            lprice = cp * .95
            #print(pQryDepthMarketData.InstrumentID,cp,uprice,lprice)
        else:
            uprice = 99999999
            lprice = 0
        dmdata = UStruct.DepthMarketData(InstrumentID=pQryDepthMarketData.InstrumentID,TradingDay=self._trading_day,UpperLimitPrice=uprice,LowerLimitPrice=lprice)
        self.spi.OnRspQryDepthMarketData(dmdata, self._suceessRspInfo, nRequestID, True)
        self.logger.info("TAStub-RQDMD:%s" % (pQryDepthMarketData.InstrumentID,))

    def RegisterFensUserInfo(self, pFensUserInfo):
        '''注册名字服务器用户信息
@param pFensUserInfo：用户信息。'''
        #未实现
        pass

    def ReqQryInvestor(self, pQryInvestor, nRequestID):
        '''请求查询投资者'''
        #未实现
        pass

    def ReqFromFutureToBankByFuture(self, pReqTransfer, nRequestID):
        '''期货发起期货资金转银行请求'''
        #未实现
        pass

    def ReqQryInstrumentMarginRate(self, pQryInstrumentMarginRate, nRequestID):
        '''请求查询合约保证金率'''
        #print("query marginrate:",pQryInstrumentMarginRate.InstrumentID)
        im = UStruct.InstrumentMarginRate(InstrumentID=pQryInstrumentMarginRate.InstrumentID,LongMarginRatioByMoney=0.1,ShortMarginRatioByMoney=0.1)
        self.spi.OnRspQryInstrumentMarginRate(im, self._suceessRspInfo, nRequestID, True)

    def ReqOrderInsert(self, pInputOrder, nRequestID):
        '''报单录入请求
            设定: (与实际不符, 只为了测试相关分支)
                普通合约名错误     CTP_REJECT
                特定合约名(CERROR)错误     EXCHANGE_REJECT
                    保证金错误       EXCHANGE_REJECT

        '''
        #print("ROI:direction:",pInputOrder.Direction)
        self.logger.info("TAStub-ROI:%s" % (str(pInputOrder),))
        print("TAStub-ROI:%s" % (str(pInputOrder),))
        #print("ROI:1:",pInputOrder.InstrumentID)
        if pInputOrder.InstrumentID[:2] == "XH":    #测试CTP_REJECT专用通道
            #调用CTP_REJECT
            self.spi.OnRspOrderInsert(pInputOrder, self._failureRspInfo, nRequestID, True)
            return

        self._orders.append(pInputOrder)
        last_tick = self._last_tick_map[pInputOrder.InstrumentID]
        pOrder = UStruct.Order(InstrumentID=pInputOrder.InstrumentID,
                               LimitPrice = pInputOrder.LimitPrice,
                               Direction = pInputOrder.Direction,
                               VolumeTotalOriginal = pInputOrder.VolumeTotalOriginal,
                               #TradingDay = self.trading_day,
                               TradingDay = last_tick.date,
                               UpdateTime = last_tick.time,
                               OrderRef = nRequestID,
                               FrontID = self._front_id,
                               SessionID = self._session_id,
                               OrderStatus = UType.THOST_FTDC_OST_Unknown,
                               CombOffsetFlag = pInputOrder.CombOffsetFlag,
                            )
        #print(pOrder)
        #efunc = self._exchange.add if pInputOrder.CombOffsetFlag == UType.THOST_FTDC_OF_Open else self._exchange.remove
        efunc = self._exchange.lock if pInputOrder.CombOffsetFlag == UType.THOST_FTDC_OF_Open else self._exchange.check_holding
        if not efunc(self._contracts[pInputOrder.InstrumentID],pInputOrder.Direction,pInputOrder.VolumeTotalOriginal,pOrder.LimitPrice):
            #保证金或平仓数不够, CTP拒绝
            logging.warning("TAStub-ROI:CTP拒绝")
            #print("TAStub-ROI:CTP拒绝")
            self.spi.OnErrRtnOrderInsert(pInputOrder, self._failureRspInfo)
            pOrder.OrderSubmitStatus = UType.THOST_FTDC_OSS_InsertRejected
            self.spi.OnRtnOrder(pOrder)
            return
        #print("TAStub-ROI:CTP ACCEPT")

        #CTP_ACCEPT
        pOrder.OrderSubmitStatus = UType.THOST_FTDC_OSS_InsertSubmitted
        self.spi.OnRtnOrder(pOrder)
        #print("ROI CTP ACCEPTED: ",pInputOrder.InstrumentID)
        if pInputOrder.InstrumentID[:2] == "XX":
            #调用EXCHANGE_REJECT,
            self.spi.OnErrRtnOrderInsert(pInputOrder, self._failureRspInfo)
            pOrder.OrderSubmitStatus = UType.THOST_FTDC_OSS_InsertRejected
            self.spi.OnRtnOrder(pOrder) #理论上应该有两次reject调用
            return

        #EXCHANGE_ACCEPT
        #print("ROI EXCHANGE ACCEPTED: ",pInputOrder.InstrumentID)
        pOrder.ExchangeID = self._exchange_id
        pOrder.OrderSysID = self.inc_order_sys_id()
        pOrder.OrderSubmitStatus = UType.THOST_FTDC_OSS_Accepted
        self.spi.OnRtnOrder(pOrder) #

        pOrder.volume_wanted = pInputOrder.VolumeTotalOriginal
        self._wlist.setdefault(pOrder.InstrumentID,[]).append(pOrder)
        #print("TAStub-ROI last_tick:",last_tick,last_tick.instrument,pOrder.InstrumentID,last_tick.price,pOrder.LimitPrice)
        self._check_trade(last_tick)
        #print("ExchangeStub.available:",self.exchange.available)
        #self._account.update_available(self.exchange.available)
        self._account.update(self._account._stamp+1,self.exchange.balance,self.exchange.available,self.exchange.margin,self.exchange.locked)
        #print(self._account.available,self.exchange.available)
        print("ExchangeStub.available:",self.exchange.available)

    def _check_trade(self,last_tick):
        if last_tick.instrument not in self._wlist:
            return
        pOrders = self._wlist[last_tick.instrument]
        untouched = []
        for pOrder in pOrders:
            if self._check_trade_one(pOrder,last_tick):
                pOrder.is_done = True
            else:
                untouched.append(pOrder)
        self._wlist[last_tick.instrument] = untouched

    def _check_trade_one(self,pOrder,last_tick):
        #print(last_tick.price,pOrder.LimitPrice)
        if pOrder.Direction == UType.THOST_FTDC_D_Buy and last_tick.price > pOrder.LimitPrice:
            return False
        elif pOrder.Direction == UType.THOST_FTDC_D_Sell and last_tick.price < pOrder.LimitPrice:
            return False

        #print("check_trade_one:True")

        #其它完成状态. 通道测试(非逻辑!!)
        #RTN_ORDER_DONE, 各种状态, 因为trade_matcher中允许多次on_done,所以这么测试分支没有问题

        #正确完成状态: 全部成功
        pOrder.VolumeTraded = pOrder.volume_wanted
        pOrder.OrderStatus = UType.THOST_FTDC_OST_AllTraded
        self.spi.OnRtnOrder(pOrder) #

        #RTN_TRADE
        pTrade = UStruct.Trade(InstrumentID=pOrder.InstrumentID,
                               Direction = pOrder.Direction,
                               ExchangeID = self._exchange_id,
                               OrderSysID = pOrder.OrderSysID,
                               #TradingDay = self.trading_day,
                               TradingDay = last_tick.date,
                               TradeDate = last_tick.date,
                               TradeTime = last_tick.time,
                               Volume = pOrder.VolumeTraded,
                               Price = last_tick.price,
                )
        self.spi.OnRtnTrade(pTrade)
        if pOrder.CombOffsetFlag == UType.THOST_FTDC_OF_Open:
            self._exchange.add(self._contracts[pOrder.InstrumentID],pOrder.Direction,pOrder.VolumeTotalOriginal,last_tick.price,pOrder.LimitPrice)
        else:
            self._exchange.remove(self._contracts[pOrder.InstrumentID],pOrder.Direction,pOrder.VolumeTotalOriginal,last_tick.price)
        #print("ROI SUCCESS: ",pInputOrder.InstrumentID)
        return True

    def ReqQryInvestorPositionCombineDetail(self, pQryInvestorPositionCombineDetail, nRequestID):
        '''请求查询投资者持仓明细'''
        #未实现
        pass

    def ReqQryTradingAccount(self, pQryTradingAccount, nRequestID):
        '''请求查询资金账户'''
        self.logger.info("TAStub-RTA:%s" % (str(pQryTradingAccount),))
        ta = UStruct.TradingAccount(TradingDay=self._trading_day,
                                    Available=self._exchange.available,
                                    Balance=self._exchange.balance,
                                    CurrMargin=self._exchange.margin,
                    )
        self.spi.OnRspQryTradingAccount(ta, self._suceessRspInfo, nRequestID, True)

    def SubscribePublicTopic(self, nResumeType):
        '''订阅公共流。
@param nResumeType 公共流重传方式
        THOST_TERT_RESTART:从本交易日开始重传
        THOST_TERT_RESUME:从上次收到的续传
        THOST_TERT_QUICK:只传送登录后公共流的内容
@remark 该方法要在Init方法前调用。若不调用则不会收到公共流的数据。'''
        #Stub不须实现
        pass

    def RegisterNameServer(self, pszNsAddress):
        '''注册名字服务器网络地址
@param pszNsAddress：名字服务器网络地址。
@remark 网络地址的格式为：“protocol://ipaddress:port”，如：”tcp://127.0.0.1:12001”。
@remark “tcp”代表传输协议，“127.0.0.1”代表服务器地址。”12001”代表服务器端口号。
@remark RegisterNameServer优先于RegisterFront'''
        #Stub不须实现
        pass

    def ReqQrySettlementInfoConfirm(self, pQrySettlementInfoConfirm, nRequestID):
        '''请求查询结算信息确认'''
        #暂不实现
        pass

    def ReqQryExchange(self, pQryExchange, nRequestID):
        '''请求查询交易所'''
        #未实现
        pass

    def ReqQryOrder(self, pQryOrder, nRequestID):
        '''请求查询报单'''
        #未实现
        pass

    def ReqQryNotice(self, pQryNotice, nRequestID):
        '''请求查询客户通知'''
        #未实现
        pass

    def ReqQryInstrument(self, pQryInstrument, nRequestID):
        '''请求查询合约'''
        self.logger.info("TAStub-RQI:%s" % (str(pQryInstrument),))
        ctype = call.cname2ctype(pQryInstrument.InstrumentID)
        contract = ContractInfo(pQryInstrument.InstrumentID,ctype)
        self._add_contract(contract)
        instr = UStruct.Instrument(InstrumentID=pQryInstrument.InstrumentID,ExchangeID=ctype.exchange_name,PriceTick=ctype.unit,VolumeMultiple=ctype.multiplier)
        self.spi.OnRspQryInstrument(instr, self._suceessRspInfo, nRequestID, True)

    def ReqQueryMaxOrderVolume(self, pQueryMaxOrderVolume, nRequestID):
        '''查询最大报单数量请求'''
        #未实现
        pass

    def ReqQryInstrumentCommissionRate(self, pQryInstrumentCommissionRate, nRequestID):
        '''请求查询合约手续费率'''
        #未实现
        pass

    def ReqTradingAccountPasswordUpdate(self, pTradingAccountPasswordUpdate, nRequestID):
        '''资金账户口令更新请求'''
        #未实现
        pass

    def ReqUserLogin(self, pReqUserLoginField, nRequestID):
        '''用户登录请求'''
        self.logger.info("TAStub-ROI:%s" % (str(pReqUserLoginField),))
        rul = UStruct.RspUserLogin(BrokerID=self._broker_id,FrontID=self._front_id,SessionID=self._session_id,MaxOrderRef=self._init_order_ref,TradingDay=self._trading_day)
        self.spi.OnRspUserLogin(rul, self._suceessRspInfo, nRequestID, True)

    def Init(self, ):
        '''初始化
@remark 初始化运行环境,只有调用后,接口才开始工作'''
        pass

    def ReqQryCFMMCTradingAccountKey(self, pQryCFMMCTradingAccountKey, nRequestID):
        '''请求查询保证金监管系统经纪公司资金账户密钥'''
        #未实现
        pass

    def ReqQryContractBank(self, pQryContractBank, nRequestID):
        '''请求查询签约银行'''
        #未实现
        pass

    def ReqUserPasswordUpdate(self, pUserPasswordUpdate, nRequestID):
        '''用户口令更新请求'''
        #未实现
        pass

    def GetTradingDay(self, ):
        '''获取当前交易日
        @retrun 获取到的交易日
        @remark 只有登录成功后,才能得到正确的交易日'''
        return self._trading_day

    #def RegisterSpi(self, pSpi):
    #    '''注册回调接口
    #        @param pSpi 派生自回调接口类的实例
    #    '''
    #    self.spi = pSpi
    #    return 1

    def ReqQryTradingNotice(self, pQryTradingNotice, nRequestID):
        '''请求查询交易通知'''
        #未实现
        pass

    def ReqQryAccountregister(self, pQryAccountregister, nRequestID):
        '''请求查询银期签约关系'''
        #未实现
        pass

    def ReqQryBrokerTradingAlgos(self, pQryBrokerTradingAlgos, nRequestID):
        '''请求查询经纪公司交易算法'''
        #未实现
        pass

    def ReqQryBrokerTradingParams(self, pQryBrokerTradingParams, nRequestID):
        '''请求查询经纪公司交易参数'''
        #未实现
        pass

    def ReqQryTransferSerial(self, pQryTransferSerial, nRequestID):
        '''请求查询转帐流水'''
        #未实现
        pass

    def ReqQryInvestorPositionDetail(self, pQryInvestorPositionDetail, nRequestID):
        '''请求查询投资者持仓明细'''
        #未实现
        pass

    def ReqQryInvestorPosition(self, pQryInvestorPosition, nRequestID):
        '''请求查询投资者持仓'''
        #未实现
        pass

    def ReqOrderAction(self, pInputOrderAction, nRequestID):
        '''报单操作请求'''
        #撤单, 交易成功, 为测试调用CTP/EXCHANGE撤单错误
        self.logger.info("TAStub-ROA:%s" % (str(pInputOrderAction),))
        target_order = None
        if pInputOrderAction.InstrumentID in self._wlist:
            i = -1
            porders = self._wlist[pInputOrderAction.InstrumentID]
            for porder in porders:
                i += 1
                if porder.ExchangeID == pInputOrderAction.ExchangeID and porder.OrderSysID == pInputOrderAction.OrderSysID:
                    target_order = porder
                    break
            if i>=0:
                del porders[i]

        if target_order:
            #print("撤单")
            self._cancelled += 1
            #取消的各种状态
            #莫名失败
            porder.OrderStatus = UType.THOST_FTDC_OST_NoTradeNotQueueing
            self.spi.OnRtnOrder(porder) #
            #部分取消
            porder.OrderStatus = UType.THOST_FTDC_OST_PartTradedNotQueueing
            self.spi.OnRtnOrder(porder) #
            #全部取消
            porder.OrderStatus = UType.THOST_FTDC_OST_Canceled
            self.spi.OnRtnOrder(porder) #
        else:   #在CancelInstruction中作了设置,正常不可能进这个分支
            #已经成交
            self._cancel_after_traded += 1
            #print("已经成交,不能撤单")
            pioa = UStruct.InputOrderAction()
            self.spi.OnRspOrderAction(pioa, self._failureRspInfo, nRequestID, True)
            poa = UStruct.OrderAction()
            self.spi.OnErrRtnOrderAction(poa, self._failureRspInfo)

    def Release(self, ):
        '''删除接口对象本身
@remark 不再使用本接口对象时,调用该函数删除接口对象'''
        pass

    def ReqQrySettlementInfo(self, pQrySettlementInfo, nRequestID):
        '''请求查询投资者结算结果'''
        si = UStruct.SettlementInfo(TradingDay=self._trading_day)
        self.spi.OnRspQrySettlementInfo(si, self._suceessRspInfo, nRequestID, True)

    def ReqQryTradingCode(self, pQryTradingCode, nRequestID):
        '''请求查询交易编码'''
        #未实现
        pass


class ExchangeStub(object):
    def __init__(self,trading_day,available=1000000,marginrate=0.12,charge=3.0/1000):
        self._holdings = {}
        self._trading_day = trading_day
        self._available = available
        self._margin = 0
        self._locked = 0
        self._marginrate = marginrate
        self._charge = charge #手续费
        #self._fhistory = open("%s/%s_%s.txt"%(LOG_PATH,"trade_history",self._trading_day),"at")
        self._log_fname = "%s/%s_%s.txt"%(LOG_PATH,"trade_history",self._trading_day)

    def finalize(self):
        pass
        #self._fhistory.close()

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

    @property
    def margin(self):
        return self._margin

    @property
    def balance(self):
        return self._available + self._margin

    @property
    def charge(self):
        return self._charge

    @property
    def locked(self):
        return self._locked

    def resume(self,items):
        """
            #deprecated, 用cjson.dump/load来解决
            items中无重复合约, item:PosInfo
        """
        for item in items:
            if item.num_long > 0:
                self._holdings[item.contract_name + '+'] = item.num_long
            if item.num_short > 0:
                self._holdings[item.contract_name + '-'] = item.num_short
        #print(self._holdings)


    def dsignal(self,direction):
        ''' 开仓，对LONG为+, SHORT为-'''
        return '+' if direction == UType.THOST_FTDC_D_Buy else '-'

    def ndsignal(self,direction):
        ''' 平仓，对LONG为-, SHORT为+，即平仓需要校验的是相反的持仓,即平空是针对的多仓'''
        return '-' if direction == UType.THOST_FTDC_D_Buy else '+'

    def lock(self,contract,direction,volume,price):
        """
            开仓前需要lock住保证金,不含手续费
        """
        ctype = contract.ctype
        margin = volume * contract.calc_margin(direction,price)
        if self._available - self.locked < margin:
            logging.warning("保证金不足以lock:%02f-%02f<%02f,volume=%d,price=%02f,unit=%d,marginrate=%02f,multiple=%d" % (self._available,self._locked,margin,volume,price,ctype.unit,ctype.base_marginrate,contract.multiple))
            return False
        self._available -= margin
        self._locked += margin
        return True

    def check_holding(self,contract,direction,volume,price):
        ukey = "%s%s" % (contract.name,self.ndsignal(direction))
        if ukey not in self._holdings or self._holdings[ukey] < volume:
            logging.warning("持仓不足以平仓,%s:%d",self._holdings,volume)
            return False
        return True

    def add(self,contract,direction,volume,price,locked_price):
        """
        :param contract:    contract_type.ContractInfo 实例
        :param direction:   UType.THOST_FTDC_D_Buy/Sell
        :param volume:      交易手数
        :param price:       最后一次tick的价格
        :return:            是否成功. 全部成功或者全部不成功
        """
        ctype = contract.ctype
        margin = volume * contract.calc_margin(direction,price)
        locked_margin = volume * contract.calc_margin(direction,locked_price)
        #if self._available < margin:
        #    return False

        self._available -= ((margin*(1+self.charge))-locked_margin) #把之前locked的拉回来
        self._margin += margin
        self._locked -= locked_margin
        ukey = "%s%s" % (contract.name,self.dsignal(direction))
        self._holdings[ukey] = self._holdings.get(ukey,0) + volume
        with open(self._log_fname,"at") as ff:
            ff.write("open:  %s:%d:%f\n" % (ukey,volume,price))
            #self._fhistory.write("open:  %s:%d:%f\n" % (ukey,volume,price))
        #print("open:  %s:%d:%f\n" % (ukey,volume,price),self._available,margin*(1+self.charge),self.charge)
        #print("开仓手续费:",margin*self.charge)
        return True

    def remove(self,contract,direction,volume,price):
        '''
            平仓时从反方向取
        '''
        ukey = "%s%s" % (contract.name,self.ndsignal(direction))
        if ukey not in self._holdings or self._holdings[ukey] < volume:
            print("in 平仓,失败")
            return False
        self._holdings[ukey] -= volume
        ctype = contract.ctype
        margin = volume * contract.calc_margin(direction,price)

        self._available += (margin*(1-self.charge))
        self._margin -= margin
        with open(self._log_fname,"at") as ff:
            ff.write("close:  %s:%d:%f\n" % (ukey,volume,price))
            #self._fhistory.write("close:  %s:%d:%f\n" % (ukey,volume,price))
        #print("close:  %s:%d:%f\n" % (ukey,volume,price),self._available,(margin*(1-self.charge)),self.charge)
        #print("平仓手续费:",margin*self.charge)
        return True
