import numpy as np
from capi.com_types import *
from .engine_model import *
import time, sys
import datetime
import copy
import math
import pandas as pd
import traceback

from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from .trigger_mgr import TriggerMgr


class TradeDateBars:
    def __init__(self, tradeDate):
        self._tradeDate = tradeDate
        self._data = []

    def updateBar(self, bar):
        assert "TradeDate" in bar and self.isInCurDataSet(bar["TradeDate"]), " error "
        if len(self._data) > 0 and bar["DateTimeStamp"] <= self._data[-1]["DateTimeStamp"]:
            self._data[-1] = bar
        else:
            self._data.append(bar)

    def getLastBar(self):
        if len(self._data) == 0:
            return None
        return self._data[-1]

    # 是否应该在当前数据结构中
    def isInCurDataSet(self, tradeDate):
        return tradeDate == self._tradeDate

    def getData(self):
        return self._data

    def getTradeDate(self):
        return self._tradeDate


class BarInfo(object):
    '''
    _curBar = 
        {
            'KLineIndex'    : value,
            'TradeDate'     : value,
            'DateTimeStamp' : value,
            'TotalQty'      : value,
            'PositionQty'   : value,
            'LastPrice'     : value,
            'KLineQty'      : value,
            'OpeningPrice'  : value,
            'HighPrice'     : value,
            'LowPrice'      : value,
            'SettlePrice'   : value,
        }
    '''
    
    def __init__(self, logger):
        self._logger = logger
        self._barList = []
        self._curBar = None

        #
        self._tradeDateBars = {}

    def _getBarValue(self, key):
        barValue = []
        for bar in self._barList:
            barValue.append(bar[key])
        return np.array(barValue)
    
    def updateBar(self, data):
        self._curBar = data
        if len(self._barList) > 0 and data["DateTimeStamp"] <= self._barList[-1]["DateTimeStamp"]:
            self._barList[-1] = data
        else:
            self._barList.append(data)

        #
        if data["TradeDate"] not in self._tradeDateBars:
            self._tradeDateBars[data["TradeDate"]] = TradeDateBars(data["TradeDate"])
        self._tradeDateBars[data["TradeDate"]].updateBar(data)

    def getCurBar(self):
        return self._curBar

    def getBarOpen(self):
        return self._getBarValue('OpeningPrice')
        
    def getBarClose(self):
        return self._getBarValue('LastPrice')

    def getBarVol(self):
        if 'KLineQty' in self._curBar:
            return self._getBarValue('KLineQty')
        else:
            return self._getBarValue('LastQty')
        #return self._getBarValue('TotalQty')

    def getBarOpenInt(self):
        return self._getBarValue('PositionQty')

    def getBarHigh(self):
        return self._getBarValue('HighPrice')
        
    def getBarLow(self):
        return self._getBarValue('LowPrice')

    def getBarTime(self):
        return self._getBarValue('DateTimeStamp')
        
    def getBarTradeDate(self):
        return self._curBar['TradeDate']

    def getBarList(self):
        return self._barList

    def getTradeDateKLine(self, tradeDate):
        if tradeDate not in self._tradeDateBars:
            return None
        return self._tradeDateBars[tradeDate].getData()
        
        
class StrategyHisQuote(object):
    '''
    功能:历史数据模型
    模型:
    _metaData = {
        'ZCE|F|SR|905' : 
        {
            'KLineReady' : False
            'KLineType'  : type
            'KLineSlice' : slice,
            'KLineData'  : [
                {
                    KLineIndex     : 0, 
                    TradeDate      : 20190405,
                    DateTimeStamp  : 20190405000000000,
                    TotalQty       : 1,
                    PositionQty    : 1,
                    LastPrice      : 4500,
                    KLineQty       : 1,
                    OpeningPrice   : 4500,
                    HighPrice      : 4500,
                    LowPrice       : 4500,
                    SettlePrice    : 4500,   
                },
                {
                    ...
                }
            ]
        }
        ...
    }
    '''
    def __init__(self, strategy, config, calc, parentDateModel):
        self._dataModel = parentDateModel
        # K线数据定义
        # response data
        self._kLineRspData = {}
        self._kLineNoticeData = {}
        self._curEarliestKLineDateTimeStamp = {}
        self._lastEarliestKLineDateTimeStamp = {}
        self._pkgEarliestKLineDateTimeStamp = {}
        self._hisLength = {}

        self._strategy = strategy
        self.logger = strategy.logger
        self._config = config
        self._calc = calc
        
        self._strategyName = strategy.getStrategyName()
        self._signalName = self._strategyName + "_Signal"
        self._textName = self._strategyName + "_Text"
        
        # 运行位置的数据
        # 和存储位置的数据不一样,存储的数据 >= 运行的数据。
        self._curBarDict = {}

        #
        self._firstRealTimeKLine = {}
        self._triggerMgr = None

    def initialize(self):
        self._contractTuple = self._config.getContract()
        # 基准合约
        self._contractNo = self._config.getBenchmark()
        self._triggerMgr = TriggerMgr(list(self._dataModel.getConfigModel().getKLineKindsInfo()), self._strategy)
        # Bar
        for record in self._config.getKLineKindsInfo():
            key = (record["ContractNo"], record["KLineType"], record["KLineSlice"])
            self._curBarDict[key] = BarInfo(self.logger)
            self._kLineRspData[key] = {
                'KLineReady': False,
                'KLineData': []
            }
            self._kLineNoticeData[key] = {
                'KLineReady': False,
                'KLineData': [],
            }
            self._hisLength[key] = 0
            self._pkgEarliestKLineDateTimeStamp[key] = -1
            self._curEarliestKLineDateTimeStamp[key] = sys.maxsize
            self._lastEarliestKLineDateTimeStamp[key] = -1
            self._firstRealTimeKLine[key] = True


    # //////////////`////////////////////////////////////////////////////

    def getHisLength(self):
        return self._hisLength
    # ////////////////////////BaseApi类接口////////////////////////
    def getBarOpenInt(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return []

        return self._curBarDict[multiContKey].getBarOpenInt()

    def getBarTradeDate(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return 0

        curBar = self._curBarDict[multiContKey].getCurBar()
        if not curBar:
            return 0
        return int(curBar['TradeDate'])

    def getBarCount(self, multiContKey):
        '''if multiContKey not in self._kLineRspData:
            return 0'''

        kLineHisData = self._kLineRspData[multiContKey]['KLineData']

        if multiContKey not in self._kLineNoticeData:
            return len(kLineHisData)

        kLineNoticeData = self._kLineNoticeData[multiContKey]['KLineData']
        if len(kLineNoticeData) == 0:
            return len(kLineHisData)

        lastHisBar = kLineHisData[-1]
        lastNoticeBar = kLineNoticeData[-1]

        return len(kLineHisData) + (lastNoticeBar['KLineIndex'] - lastHisBar['KLineIndex'])

    def getBarStatus(self, multiContKey):
        if multiContKey not in self._kLineRspData:
            return -1
        
        kLineHisData = self._kLineRspData[multiContKey]['KLineData']
        if len(kLineHisData) == 0:
            return -1
        firstIndex = kLineHisData[0]['KLineIndex']
        lastIndex  = kLineHisData[-1]['KLineIndex']
        
        if multiContKey in self._kLineNoticeData:
            kLineNoticeData = self._kLineNoticeData[multiContKey]['KLineData']
            if len(kLineNoticeData) > 0:
                lastIndex = kLineNoticeData[-1]['KLineIndex']

        curBar = self._curBarDict[multiContKey].getCurBar()
        curBarIndex = curBar['KLineIndex']
        
        if curBarIndex == firstIndex:
            return 0
        elif curBarIndex >= lastIndex:
            return 2
        else:
            return 1

    def isHistoryDataExist(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return False

        return True if len(self._kLineRspData[multiContKey]) else False

    def getBarDate(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return 0
        curBar = self._curBarDict[multiContKey].getCurBar()
        if not curBar:
            return 0
        return int(curBar['DateTimeStamp'] // 1000000000)

    def getBarTime(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return 0
        curBar = self._curBarDict[multiContKey].getCurBar()
        if not curBar:
            return 0
        timeStamp = str(curBar['DateTimeStamp'])
        return float(timeStamp[-9:])/1000000000

    def getBarOpen(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return np.array([])
            
        return self._curBarDict[multiContKey].getBarOpen()

    def getBarClose(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return np.array([])
            
        return self._curBarDict[multiContKey].getBarClose()

    def getOpenD(self, daysAgo, multiContKey):
        openList = self.getBarOpen(multiContKey)

        if len(openList) == 0:
            contNo = multiContKey[0]
            raise Exception("请确保在策略的initialize方法中使用SetBarInterval(\"%s\", 'D', 1)方法订阅%s合约的日线信息"%(contNo, contNo))

        if daysAgo+1 > len(openList):
            return -1
        return openList[-(daysAgo + 1)]

    def getCloseD(self, daysAgo, multiContKey):
        priceList = self.getBarClose(multiContKey)

        if len(priceList) == 0:
            contNo = multiContKey[0]
            raise Exception("请确保在策略的initialize方法中使用SetBarInterval(\"%s\", 'D', 1)方法订阅%s合约的日线信息"%(contNo, contNo))

        if daysAgo+1 > len(priceList):
            return -1
        return priceList[-(daysAgo + 1)]

    def getHighD(self, daysAgo, multiContKey):
        priceList = self.getBarHigh(multiContKey)

        if len(priceList) == 0:
            contNo = multiContKey[0]
            raise Exception("请确保在策略的initialize方法中使用SetBarInterval(\"%s\", 'D', 1)方法订阅%s合约的日线信息"%(contNo, contNo))

        if daysAgo+1 > len(priceList):
            return -1
        return priceList[-(daysAgo + 1)]

    def getLowD(self, daysAgo, multiContKey):
        priceList = self.getBarLow(multiContKey)

        if len(priceList) == 0:
            contNo = multiContKey[0]
            raise Exception("请确保在策略的initialize方法中使用SetBarInterval(\"%s\", 'D', 1)方法订阅%s合约的日线信息"%(contNo, contNo))

        if daysAgo+1 > len(priceList):
            return -1
        return priceList[-(daysAgo + 1)]

    def getBarVol(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return np.array([])
            
        return self._curBarDict[multiContKey].getBarVol()
        
    def getBarHigh(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return np.array([])
        return self._curBarDict[multiContKey].getBarHigh()
        
    def getBarLow(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return np.array([])
        return self._curBarDict[multiContKey].getBarLow()

    def getBarTimeList(self, multiContKey):
        if multiContKey not in self._curBarDict:
            return np.array([])

        barTimeList = self._curBarDict[multiContKey].getBarTime()
        return np.array([float(barTime)/1000000000 for barTime in list(barTimeList)])

    def getHisData(self, dataType, multiContKey, maxLength):
        if dataType not in (BarDataClose, BarDataOpen, BarDataHigh,
                            BarDataLow, BarDataMedian, BarDataTypical,
                            BarDataWeighted, BarDataVol, BarDataOpi,
                            BarDataTime):
            return []

        methodMap = {
            BarDataClose    : self.getBarClose,
            BarDataOpen     : self.getBarOpen,
            BarDataHigh     : self.getBarHigh,
            BarDataLow      : self.getBarLow,
            BarDataMedian   : self.getBarMedian,
            BarDataTypical  : self.getBarTypical,
            BarDataWeighted : self.getBarWeighted,
            BarDataVol      : self.getBarVol,
            BarDataOpi      : self.getBarOpenInt,
            BarDataTime     : self.getBarTimeList,
        }

        numArray = methodMap[dataType](multiContKey)

        return numArray if len(numArray) <= maxLength else numArray[-maxLength : ]

    def getHisBarsInfo(self, multiContKey, maxLength):
        if maxLength is not None and maxLength <= 0:
            return []

        if multiContKey not in self._curBarDict:
            return []

        barInfo = self._curBarDict[multiContKey]
        barInfoList = barInfo._barList
        if not barInfoList:
            return []

        return barInfoList if maxLength is None or len(barInfoList) <= maxLength else barInfoList[-maxLength : ]

    #//////////////////////////////////内部接口//////////////////////////////////

    # 获取存储位置最后一根k线的交易日
    def getLastTradeDate(self):
        result = {}
        for contractNo in self._contractTuple:
            lastKLine, _ = self.getLastStoredKLine(contractNo)
            if lastKLine is None:
                result[contractNo] = None
            else:
                result[contractNo] = lastKLine["TradeDate"]
        return result

    def getLastStoredKLine(self, key):
        noticeKLineDatas = self._kLineNoticeData[key]["KLineData"]
        rspKLineDatas = self._kLineRspData[key]["KLineData"]
        if len(noticeKLineDatas) > 0:
            return noticeKLineDatas[-1], KLineFromRealTime
        elif len(rspKLineDatas)>0:
            return rspKLineDatas[-1], KLineFromHis
        else:
            return None, None

    def setLastStoredKLineStable(self, key):
        noticeKLineDatas = self._kLineNoticeData[key]["KLineData"]
        if len(noticeKLineDatas) > 0:
            noticeKLineDatas[-1]["IsKLineStable"] = True
        else:
            pass

    def getLastRunKLine(self, contractNo):
        assert contractNo in self._curBarDict, "error"
        barManager = self._curBarDict[contractNo]
        return barManager.getCurBar()

    def getBarMedian(self, contNo):
        high = self.getBarHigh(contNo)
        low = self.getBarLow(contNo)
        minLength = min(len(high), len(low))
        if minLength == 0:
            return []
        medianList = []
        for i in range(0, minLength):
            median = (high[i] + low[i]) / 2
            medianList.append(median)
        return np.array(medianList)

    def getBarTypical(self, contNo):
        high = self.getBarHigh(contNo)
        low = self.getBarLow(contNo)
        close = self.getBarClose(contNo)
        minLength = min(len(high), len(low), len(close))
        if minLength == 0:
            return []
        typicalList = []
        for i in range(0, int(minLength)):
            typical = (high[i] + low[i] + close[i]) / 3
            typicalList.append(typical)
        return np.array(typicalList)

    def getBarWeighted(self, contNo):
        high = self.getBarHigh(contNo)
        low = self.getBarLow(contNo)
        open = self.getBarOpen(contNo)
        close = self.getBarClose(contNo)
        minLength = min(len(high), len(low), len(open), len(close))
        if minLength == 0:
            return []
        weightedList = []
        for i in range(0, minLength):
            weighted = (high[i] + low[i] + open[i] + close[i]) / 4
            weightedList.append(weighted)
        return np.array(weightedList)

    #////////////////////////参数设置类接口///////////////////////
        
    def _getKLineType(self):
        return self._config.getKLineType()
        
    def _getKLineSlice(self):
        return self._config.getKLineSlice()

    def _getKLineCount(self, sampleDict):
        if not sampleDict['UseSample']:
            return 1

        if sampleDict['KLineCount'] > 0:
            return sampleDict['KLineCount']

        if len(sampleDict['BeginTime']) > 0:
            return sampleDict['BeginTime']

        if sampleDict['AllK']:
            nowDateTime = datetime.now()
            if self._getKLineType() == EEQU_KLINE_DAY:
                threeYearsBeforeDateTime = nowDateTime - relativedelta(years = 3)
                threeYearsBeforeStr = datetime.strftime(threeYearsBeforeDateTime, "%Y%m%d")
                return threeYearsBeforeStr
            elif self._getKLineType() == EEQU_KLINE_HOUR or self._getKLineType() == EEQU_KLINE_MINUTE:
                oneMonthBeforeDateTime = nowDateTime - relativedelta(months = 1)
                oneMonthBeforeStr = datetime.strftime(oneMonthBeforeDateTime, "%Y%m%d")
                return oneMonthBeforeStr
            elif self._getKLineType() == EEQU_KLINE_SECOND:
                oneWeekBeforeDateTime = nowDateTime - relativedelta(days = 7)
                oneWeekBeforeStr = datetime.strftime(oneWeekBeforeDateTime, "%Y%m%d")
                return oneWeekBeforeStr
            else:
                raise NotImplementedError

    # //////////////////////////K线处理接口////////////////////////
    def reqAndSubKLineByCount(self, contractNo, kLineType, kLineSlice, count, notice):
        # print("请求k线", contractNo, kLineType, kLineSlice, count)
        # 请求历史K线阶段先不订阅
        event = Event({
            'EventCode'   : EV_ST2EG_SUB_HISQUOTE,
            'StrategyId'  : self._strategy.getStrategyId(),
            'ContractNo'  : contractNo,
            'KLineType'   : kLineType,
            'KLineSlice'  : kLineSlice,
            'Data'        : {
                    'ReqCount'   :  count,
                    'ContractNo' :  contractNo,
                    'KLineType'  :  kLineType,
                    'KLineSlice' :  kLineSlice,
                    'NeedNotice' :  notice,
                },
            })

        self._strategy.sendEvent2Engine(event)

    # '''向9.5请求所有合约历史数据'''
    # 请求历史k线,同时订阅即时k线, 参数全部合法, 至少请求一根
    def reqAndSubKLine(self):
        self._isReqByDate = {}
        self._reqBeginDate = {}
        self._isReqByDateEnd = {}
        self._reqKLineTimes = {}

        dateTimeStampLength = len("20190326143100000")
        for record in self._config.getKLineSubsInfo():
            countOrDate = record['BarCount']
            key = (record['ContractNo'], record['KLineType'], record['KLineSlice'])
            # print(" count or date is ", countOrDate)
            if isinstance(countOrDate, int):
                self._isReqByDate[key] = False
                self.reqAndSubKLineByCount(key[0], key[1], key[2], countOrDate, EEQU_NOTICE_NEED)
            else:
                self._isReqByDate[key] = True
                self._isReqByDateEnd[key] = False
                self._reqBeginDate[key] = int(countOrDate + (dateTimeStampLength - len(countOrDate)) * '0')
                self._reqKLineTimes[key] = 1
                count = self._reqKLineTimes[key] * 4000
                self.reqAndSubKLineByCount(key[0], key[1], key[2], count, EEQU_NOTICE_NOTNEED)

    def _handleKLineRspData(self, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        self._insertHisRspData(event)
        if self.isHisQuoteRspEnd(event):
            self._reIndexHisRspData(key)
            self._hisLength[key] = len(self._kLineRspData[key]["KLineData"])
            # if key == self._config.getKLineShowInfoSimple():
            #     print(self._kLineRspData[key]["KLineData"])

    # 当 not self._reqByDateEnd时,更新
    def _updateRspDataRefDTS(self, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        assert self._isReqByDate[key] and not self._isReqByDateEnd[key], "error"
        dataList = event.getData()
        contractNo = event.getContractNo()
        # update current package earliest KLine DateTimeStamp
        if len(dataList) == 0:
            pass
        else:
            self._pkgEarliestKLineDateTimeStamp[key] = dataList[-1]["DateTimeStamp"]
        # update current req earliest KLine DateTimeStamp
        if event.isChainEnd() and self._pkgEarliestKLineDateTimeStamp[key]<self._curEarliestKLineDateTimeStamp[key]:
            self._curEarliestKLineDateTimeStamp[key] = self._pkgEarliestKLineDateTimeStamp[key]

    def _handleKLineRspByDate(self, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        if not self._isReqByDateEnd[key]:
            self._insertHisRspData(event)
            self._updateRspDataRefDTS(event)
            if event.isChainEnd():
                self._isReqByDateContinue(event)
        else:
            self._handleKLineRspData(event)

    #
    def _isReqByDateContinue(self,  event):
        assert event.isChainEnd(), " error call"
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        if self._curEarliestKLineDateTimeStamp[key] <= self._reqBeginDate[key]:
            self._isReqByDateEnd[key] = True
            self.reqAndSubKLineByCount(key[0], key[1], key[2], self._reqKLineTimes[key] * 4000, EEQU_NOTICE_NEED)
        # 9.5 lack data
        elif self._curEarliestKLineDateTimeStamp[key] == self._lastEarliestKLineDateTimeStamp[key]:
            self._isReqByDateEnd[key] = True
            self.reqAndSubKLineByCount(key[0], key[1], key[2], self._reqKLineTimes[key] * 4000, EEQU_NOTICE_NEED)
        # local lack data
        elif self._curEarliestKLineDateTimeStamp[key] > self._reqBeginDate[key]:
            self._reqKLineTimes[key] += 1
            self.reqAndSubKLineByCount(key[0], key[1], key[2], self._reqKLineTimes[key] * 4000, EEQU_NOTICE_NOTNEED)
            self._lastEarliestKLineDateTimeStamp[key] = self._curEarliestKLineDateTimeStamp[key]
        else:
            raise IndexError("can't be this case")

    def _handleKLineRspByCount(self, event):
        self._handleKLineRspData(event)

    # response 数据
    def onHisQuoteRsp(self, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        # print("key = ", key, len(event.getData()), event.isChainEnd(), key)
        # assert kindInfo in self._config.getKLineKindsInfo(), " Error "
        if not self._isReqByDate[key]:                        # req by count
            self._handleKLineRspByCount(event)
        else:                                                 # req by date
            self._handleKLineRspByDate(event)

    def isHisQuoteRspEnd(self, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        if event.isChainEnd() and not self._isReqByDate[key]:
            return True
        if event.isChainEnd() and self._isReqByDate[key] and self._isReqByDateEnd[key]:
            return True
        return False

    # 更新response 数据
    def _insertHisRspData(self, event):
        contNo = event.getContractNo()
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        localRspKLineData = self._kLineRspData[key]["KLineData"]

        kLineRspMsg = event.getData()
        # print("datalist is ", dataList)
        for kLineData in kLineRspMsg:
            kLineData["ContractNo"] = event.getContractNo()
            kLineData["KLineType"] = event.getKLineType()
            kLineData['KLineSlice'] = event.getKLineSlice()
            kLineData["Priority"] = self._config.getPriority(key)
            if key[1] == EEQU_KLINE_TICK and key[2] == 0:
                kLineData["HighPrice"] = kLineData["LastPrice"]
                kLineData["LowPrice"] = kLineData["LastPrice"]
                kLineData["OpeningPrice"] = kLineData["LastPrice"]
            if self._isReqByDate[key]:
                if len(localRspKLineData) == 0 or (len(localRspKLineData) >= 1 and kLineData["DateTimeStamp"]<localRspKLineData[0]["DateTimeStamp"] and \
                kLineData["DateTimeStamp"] >= self._reqBeginDate[key]):
                    localRspKLineData.insert(0, kLineData)
            else:
                if len(localRspKLineData) == 0 or (len(localRspKLineData) >= 1 and kLineData["DateTimeStamp"]<localRspKLineData[0]["DateTimeStamp"]):
                    localRspKLineData.insert(0, kLineData)

    def _reIndexHisRspData(self, key):
        dataDict = self._kLineRspData[key]
        rfdataList = dataDict['KLineData']
        dataDict['KLineReady'] = True
        for i, record in enumerate(rfdataList):
            rfdataList[i]['KLineIndex'] = i+1

    def _handleKLineNoticeData(self, localDataList, event):
        # print("1111111111: ", localDataList)
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())

        # notice数据,直接加到队尾
        for data in event.getData():
            isNewKLine = True
            data["IsKLineStable"] = False
            if key[1] == EEQU_KLINE_TICK and key[2] == 0:
                data["HighPrice"] = data["LastPrice"]
                data["LowPrice"] = data["LastPrice"]
                data["OpeningPrice"] = data["LastPrice"]
            storedLastKLine, lastKLineSource = self.getLastStoredKLine(key)
            # 没有数据,索引取回测数据的最后一条数据的索引,没有数据从1开始
            if storedLastKLine is None:
                data["KLineIndex"] = 1
                localDataList.append(data)
            else:
                lastKLineIndex = storedLastKLine["KLineIndex"]
                lastKLineDTS = storedLastKLine["DateTimeStamp"]
                if lastKLineDTS == data["DateTimeStamp"]:
                    data["KLineIndex"] = lastKLineIndex
                    isNewKLine = False
                    self._handleSameKLine(localDataList, data, lastKLineSource)
                elif lastKLineDTS < data["DateTimeStamp"]:
                    data["KLineIndex"] = lastKLineIndex+1
                    self.setLastStoredKLineStable(key)
                    localDataList.append(data)
                    # print(" 存储位置 new k line index =", data["KLineIndex"])
                else:
                    self.logger.error("error DateTimeStamp on StrategyHisQuote notice")

            # 处理触发
            # 一定要先填触发事件,在填充数据。
            # 否则触发有可能会覆盖
            isRealTimeStatus = self._strategy.isRealTimeStatus()
            orderWay = str(self._config.getSendOrder())
            kLineTrigger = self._config.hasKLineTrigger()

            # print(isRealTimeStatus, orderWay, orderWay == SendOrderRealTime, orderWay == SendOrderStable)
            if not kLineTrigger:
                pass
            elif not isRealTimeStatus and len(localDataList) >= 2 and localDataList[-2]["IsKLineStable"] and isNewKLine:
                self._sendHisKLineTriggerEvent(key, localDataList[-2])
            elif isRealTimeStatus:
                # 一种特殊情况
                if self._firstRealTimeKLine[key] and isNewKLine and len(localDataList) >= 2 and localDataList[-2]["IsKLineStable"] and orderWay == SendOrderRealTime:
                    self._sendHisKLineTriggerEvent(key, localDataList[-2])
                self._firstRealTimeKLine[key] = False
                # 处理实时触发和k线稳定后触发
                if orderWay == SendOrderRealTime:
                    self._sendRealTimeKLineTriggerEvent(key, localDataList[-1])
                elif orderWay == SendOrderStable and len(localDataList) >= 2 and localDataList[-2]["IsKLineStable"] and isNewKLine:
                    #print("+++++++++++++")
                    self._sendRealTimeKLineTriggerEvent(key, localDataList[-2])
            else:
                pass
            #
            # # 实时阶段填充最新数据。
            # # 触发和填充都更新运行位置数据
            # # 但是仅填充数据事件向9.5发送数据
            if isRealTimeStatus:
                self._fillDataWhenRealTime(key, localDataList[-1])

    def _handleSameKLine(self, localDataList, data, lastKLineSource):
        if lastKLineSource == KLineFromHis:
            localDataList.append(data)
        elif lastKLineSource == KLineFromRealTime:
            localDataList[-1] = data

    def _fillDataWhenRealTime(self, key, data):
        event = Event({
            "EventCode": ST_TRIGGER_FILL_DATA,
            "ContractNo": key[0],
            "KLineType": key[1],
            "KLineSlice": key[2],
            "Data": {
                "Data": data,
                "Status": ST_STATUS_CONTINUES
            }
        })
        # print("[on his quote notice]填充k线到队列", data["KLineIndex"], data)
        self._strategy.sendTriggerQueue(event)
        return

    # # 填充k线
    # def _sendKLine(self, key, data, isRealTimeStatus):
    #     if not isRealTimeStatus and data["IsKLineStable"]:
    #         event = Event({
    #             "EventCode" : ST_TRIGGER_FILL_DATA,
    #             "ContractNo": key[0],
    #             "KLineType" : key[1],
    #             "KLineSlice": key[2],
    #             "Data": {
    #                 "Data": data,
    #                 "Status": ST_STATUS_HISTORY
    #             }
    #         })
    #         self._strategy.sendTriggerQueue(event)
    #         return
    #
    #     if isRealTimeStatus:


    def _sendHisKLineTriggerEvent(self, key, data):
        if not data["IsKLineStable"] or not self._config.hasKLineTrigger() or key not in self._config.getKLineTriggerInfoSimple():
            return

        assert key[1] is not None, "k line type error"
        event = Event({
            'EventCode': ST_TRIGGER_HIS_KLINE,
            'ContractNo': key[0],
            "KLineType": key[1],
            "KLineSlice": key[2],
            'Data': {
                "Data": data,
                "TradeDate": data["TradeDate"],
                "DateTimeStamp": data["DateTimeStamp"],
            }
        })
        self._strategy.sendTriggerQueue(event)

    def _sendRealTimeKLineTriggerEvent(self, key, data):
        self._triggerMgr.updateData(key, data)
        kLineTrigger = self._config.hasKLineTrigger()
        if not kLineTrigger or key not in self._config.getKLineTriggerInfoSimple():
            return

        assert self._strategy.isRealTimeStatus(), " Error "
        orderWay = str(self._config.getSendOrder())
        if orderWay == SendOrderRealTime or (orderWay == SendOrderStable and data["IsKLineStable"]):
            if not self._triggerMgr.isAllDataReady(key[0]):
                return
            self._sendSyncTriggerEvent(key[0])
            self._triggerMgr.resetAllData(key[0])
            return

        # if orderWay == SendOrderStable and data["IsKLineStable"]:
        #     # **************************同步数据
        #     self._triggerMgr.updateData(key, data)
        #     if not self._triggerMgr.isAllDataReady(key[0]):
        #         return
        #     self._sendSyncTriggerEvent(key[0])
        #     self._triggerMgr.resetAllData(key[0])
        #     return

    def onHisQuoteNotice(self, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        kindInfo = {"ContractNo": key[0], "KLineType": key[1], "KLineSlice": key[2]}
        # print("kind = ", event.getData()[0]["DateTimeStamp"], kindInfo)
        # 丢掉
        if not self._kLineRspData[key]["KLineReady"]:
            return

        # print("[on his quote notice ]", kindInfo, len(event.getData()), event.getData()[0]["DateTimeStamp"])
        assert kindInfo in self._config.getKLineKindsInfo(), " Error "
        localDataList = self._kLineNoticeData[key]['KLineData']
        self._handleKLineNoticeData(localDataList, event)

    # ///////////////////////////回测接口////////////////////////////////
    def _isAllReady(self):
        for record in self._config.getKLineKindsInfo():
            key = (record["ContractNo"], record["KLineType"], record["KLineSlice"])
            if not self._kLineRspData[key]["KLineReady"]:
                return False
        return True

    def _switchKLine(self, key=None):
        if key is None:
            key = self._config.getKLineShowInfoSimple()
        event = Event({
            "EventCode" :EV_ST2EG_SWITCH_STRATEGY,
            'StrategyId': self._strategy.getStrategyId(),
            'Data':
                {
                    'StrategyName': self._strategy.getStrategyName(),
                    'ContractNo'  : key[0],
                    'KLineType'   : key[1],
                    'KLineSlice'  : key[2],
                }
        })
        
        self._strategy.sendEvent2Engine(event)
        
    def _addSingleKLine(self, data):
        event = Event({
            "EventCode"  : EV_ST2EG_UPDATE_KLINEDATA,
            "StrategyId" : self._strategy.getStrategyId(),
            "KLineType"  : self._getKLineType(),
            "KLineSlice" : self._getKLineSlice(),
            "Data": {
                'Count'  : 1,
                "Data"   : [data,],
            }
        })
        # print("问题1:中间阶段:", data["KLineIndex"], data["DateTimeStamp"])
        self._strategy.sendEvent2Engine(event)
        
    def _addSignal(self):
        event = Event({
            "EventCode":EV_ST2EG_ADD_KLINESIGNAL,
            'StrategyId':self._strategy.getStrategyId(),
            "Data":{
                'ItemName':'EquantSignal',
                'Type': EEQU_INDICATOR,
                'Color': 0,
                'Thick': 1,
                'OwnAxis': EEQU_ISNOT_AXIS,
                'Param': [],
                'ParamNum': 0,
                'Groupid': 0,
                'GroupName':'Equant',
                'Main': EEQU_IS_MAIN,
            }
        })
        self._strategy.sendEvent2Engine(event)
    
    def _updateCurBar(self, key, data):
        '''更新当前Bar值'''
        self._curBarDict[key].updateBar(data)
        
    def _updateOtherBar(self, otherContractDatas):
        '''根据指定合约Bar值,更新其他合约bar值'''
        for otherContract, otherContractData in otherContractDatas.items():
            if otherContract not in self._curBarDict:
                self._curBarDict[otherContract] = BarInfo(self.logger)
            self._curBarDict[otherContract].updateBar(otherContractData)
    
    def _sendFlushEvent(self):
        event = Event({
            "EventCode": EV_ST2EG_UPDATE_STRATEGYDATA,
            "StrategyId": self._strategy.getStrategyId(),
        })
        self._strategy.sendEvent2Engine(event)
        
    def getCurBar(self, key=None):
        if not key:
            key = self._config.getKLineShowInfoSimple()
        return self._curBarDict[key].getCurBar()

    def printRspReady(self):
        for record in self._config.getKLineKindsInfo():
            key = (record["ContractNo"], record["KLineType"], record["KLineSlice"])
            print(record["ContractNo"], self._kLineRspData[key]["KLineReady"])

    # 30天月线和255天年线日期无效处理
    def replaceDateStr(self, datestr):
        if len(datestr) != 17:
            return datestr
            
        sl = list(datestr)
        
        # 处理无效月
        if sl[4] == '0' and sl[5] == '0':
            sl[5] = '1'
        # 处理无效日
        if sl[6] == '0' and sl[7] == '0':
            sl[7] = '1'
            
        return ''.join(sl)

    def runReport(self, context, handle_data):
        # 不使用历史K线,也需要切换
        # 切换K线
        key = self._config.getKLineShowInfoSimple()

        self._switchKLine(key)
        # 增加信号线
        self._addSignal()
        self._sendFlushEvent()

        while not self._isAllReady():
            # print("waiting for data arrived ")
            # self.printRspReady()
            time.sleep(1)

        allHisData = []
        for record in self._config.getKLineKindsInfo():
            key = (record["ContractNo"], record["KLineType"], record["KLineSlice"])
            #self.logger.error(key)
            hisData = self._kLineRspData[key]["KLineData"]
            allHisData.extend(hisData)

        if len(allHisData) == 0:
            self.logger.error("没有数据,请检查SetBarInterval函数")
            return

        newDF = pd.DataFrame(allHisData)
        test = newDF[["DateTimeStamp", "KLineType", "KLineSlice"]].values
        effectiveDTS = []
        for i, record in enumerate(test):
            curBarDTS = datetime.strptime(self.replaceDateStr(str(record[0])), "%Y%m%d%H%M%S%f")
            if record[1] == EEQU_KLINE_MINUTE:
                curEffectiveDTS = curBarDTS-relativedelta(minutes=record[2])
            elif record[1] == EEQU_KLINE_DAY:
                curEffectiveDTS = curBarDTS-relativedelta(days=record[2])
                if curEffectiveDTS.isoweekday() == 6:
                    curEffectiveDTS = curEffectiveDTS - relativedelta(days=1)
                elif curEffectiveDTS.isoweekday() == 7:
                    curEffectiveDTS = curEffectiveDTS - relativedelta(days=2)
            elif record[1] == EEQU_KLINE_TICK:
                curEffectiveDTS = curBarDTS-relativedelta(seconds=record[2])
            else:
                raise NotImplementedError("未实现的k线类型支持")
            effectiveDTS.append(curEffectiveDTS.strftime("%Y%m%d%H%M%S%f"))

        newDF["DateTimeStampForSort"] = effectiveDTS
        newDF.sort_values(['TradeDate', 'DateTimeStampForSort', 'Priority'], ascending=[True, True, False], inplace=True)
        newDF.reset_index(drop=True, inplace=True)

        # print("new df is ")
        # print(newDF[["TradeDate", "DateTimeStampForSort", "DateTimeStamp", "KLineType"]])
        allHisData = newDF.to_dict(orient="index")

        # print(newDF[["ContractNo", "TradeDate", "DateTimeStamp"]])
        beginTime = datetime.now()
        beginTimeStr = datetime.now().strftime('%H:%M:%S.%f')
        print('**************************** run his begin', len(allHisData))
        self.logger.info('[runReport] run report begin')
        beginPos = 0
        endPos = 0
        for index, row in allHisData.items():
            key = (row["ContractNo"], row["KLineType"], row["KLineSlice"])
            isShow = key == self._config.getKLineShowInfoSimple()
            lastBar = self.getCurBar(key)
            self._updateCurBar(key, row)
            curBar = self.getCurBar(key)
            if lastBar is None or math.fabs(curBar["LastPrice"]-lastBar["LastPrice"])>1e-4:
                self._calcProfitWhenHis()
            if not self._config.hasKLineTrigger():
                continue

            if isShow or key in self._config.getKLineTriggerInfoSimple():
                args = {
                    "Status": ST_STATUS_HISTORY,
                    "TriggerType":ST_TRIGGER_HIS_KLINE,
                    "ContractNo":key[0],
                    "KLineType":key[1],
                    "KLineSlice":key[2],
                    "TradeDate":row["TradeDate"],
                    "DateTimeStamp":row["DateTimeStamp"],
                    "TriggerData":row,
                }
                self._strategy.setCurTriggerSourceInfo(args)
                context.setCurTriggerSourceInfo(args)
                handle_data(context)

            # 处理历史回测阶段止损止盈            
            if key[1] not in self._config.getStopWinKtBlack():
                self._stopWinOrLose(key[0], True, row)
                self._stopFloatWinLose(key[0], True, row)

            # # 要显示的k线
            if isShow:
                endPos += 1

            # 发送刷新事件
            if isShow and endPos % 50 == 0:
                batchKLine = self._curBarDict[key].getBarList()[beginPos:endPos]
                self._addBatchKLine(batchKLine)
                self._sendFlushEvent()
                beginPos = endPos
                tradeDate = self._curBarDict[key].getCurBar()["TradeDate"]

            # 收到策略停止或退出信号, 退出历史回测
            if self._strategy._isExit():
                break
        #
        showKey = self._config.getKLineShowInfoSimple()
        if endPos != beginPos:
            batchKLine = self._curBarDict[showKey].getBarList()[beginPos:]
            self._addBatchKLine(batchKLine)
        self._sendFlushEvent()
        endTime = datetime.now()
        endTimeStr = datetime.now().strftime('%H:%M:%S.%f')
        self.logger.debug('[runReport] run report completed!')
        # self.logger.debug('[runReport] run report completed!, k线数量: {}, 耗时: {}s'.format(len(allHisData), endTime-beginTime))
        # print('**************************** run his end')

    def _addBatchKLine(self, data):
        event = Event({
            "EventCode": EV_ST2EG_NOTICE_KLINEDATA,
            "StrategyId": self._strategy.getStrategyId(),
            "KLineType": self._getKLineType(),
            "KLineSlice": self._getKLineSlice(),
            "Data": {
                'Count': len(data),
                "Data": copy.deepcopy(data),
            }
        })
        # print("历史回测阶段:", data["KLineIndex"])
        self._strategy.sendEvent2Engine(event)

    # 在跑历史回测期间积攒的实时数据,但是作为历史回测, 因为有效期已过。
    def runVirtualReport(self, context, handle_data, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        kLineData = event.getData()["Data"]
        isShow = key==self._config.getKLineShowInfoSimple()
        # **************************
        lastBar = self.getCurBar(key)
        self._updateCurBar(key, kLineData)
        curBar = self.getCurBar(key)
        if lastBar is None or math.fabs(curBar["LastPrice"] - lastBar["LastPrice"]) > 1e-4:
            self._calcProfitWhenHis()
        # **************************
        # print(key, self._config.getKLineTriggerInfoSimple(), key in self._config.getKLineTriggerInfoSimple())
        if self._config.hasKLineTrigger() and key in self._config.getKLineTriggerInfoSimple():
            args = {
                "Status": ST_STATUS_HISTORY,
                "TriggerType": ST_TRIGGER_HIS_KLINE,
                "ContractNo": key[0],
                "KLineType": key[1],
                "KLineSlice": key[2],
                "TradeDate": kLineData["TradeDate"],
                "DateTimeStamp": kLineData["DateTimeStamp"],
                "TriggerData": kLineData
            }
            self._strategy.setCurTriggerSourceInfo(args)
            context.setCurTriggerSourceInfo(args)
            handle_data(context)

        # 处理中间阶段止损止盈,按照历史回测止损止盈
        if key[1] not in self._config.getStopWinKtBlack():
            self._stopWinOrLose(key[0], True, kLineData)
            self._stopFloatWinLose(key[0], True, kLineData)

        # **********************************
        if isShow:
            self._addSingleKLine(kLineData)
            self._sendFlushEvent()

    def _calcProfitWhenHis(self):
        priceInfos = {}
        curTriggerInfo = self._strategy.getCurTriggerSourceInfo()

        if curTriggerInfo is None or curTriggerInfo["KLineType"] is None or curTriggerInfo["KLineSlice"] is None:
            return
        key = (curTriggerInfo["ContractNo"], curTriggerInfo["KLineType"], curTriggerInfo["KLineSlice"])

        curBar = self._curBarDict[key].getCurBar()
        contNo = self._dataModel.getIndexMap(key[0])
        priceInfos[contNo] = {
            "LastPrice": curBar['LastPrice'],
            "HighPrice": curBar['HighPrice'],
            "LowPrice": curBar['LowPrice'],
            "DateTimeStamp": curBar['DateTimeStamp'],
            "TradeDate": curBar['TradeDate'],
            "LastPriceSource": KLineFromHis,
        }

        self._calc.calcProfit([contNo], priceInfos)

    def drawBatchHisKine(self, data):
        self.sendAllHisKLine(data)
        self._sendFlushEvent()

    def sendAllHisKLine(self, data):
        if len(data) == 0:
            return
        # print("len = ", len(data))
        event = Event({
            "EventCode": EV_ST2EG_NOTICE_KLINEDATA,
            "StrategyId": self._strategy.getStrategyId(),
            "KLineType": self._getKLineType(),
            "Data": {
                'Count': len(data),
                "Data": data,
            }
        })
        self._strategy.sendEvent2Engine(event)

    # 填充k线, 发送到9.5
    def runFillData(self, context, handle_data, event):
        key = (event.getContractNo(), event.getKLineType(), event.getKLineSlice())
        data = event.getData()["Data"]
        self._updateCurBar(key, data)
        # print("[run fill data] ", data["KLineIndex"], data["KLineQty"])
        if key == self._config.getKLineShowInfoSimple():
            self._sendRealTimeKLine2Client(key, data)
            # print(self._strategy.isRealTimeStatus(), self._strategy._runStatus, self._strategy._runRealTimeStatus, self._strategy.isRealTimeAsHisStatus())
            self._sendFlushEvent()

    def checkTriggerEvent(self, eventCode):
        if eventCode == ST_TRIGGER_SANPSHOT_FILL:
            return self._config.hasSnapShotTrigger()
        elif eventCode == ST_TRIGGER_KLINE:
            return self._config.hasKLineTrigger()
        elif eventCode in (ST_TRIGGER_TRADE_ORDER, ST_TRIGGER_TRADE_MATCH):
            return self._config.hasTradeTrigger()
        elif eventCode == ST_TRIGGER_TIMER:
            return self._config.hasTimerTrigger()
        elif eventCode == ST_TRIGGER_CYCLE:
            return self._config.hasCycleTrigger()
        
        return True

    # ST_STATUS_CONTINUES_AS_REALTIME 阶段
    def runRealTime(self, context, handle_data, event):
        eventCode = event.getEventCode()
        assert eventCode in [ST_TRIGGER_KLINE, ST_TRIGGER_TRADE_ORDER, ST_TRIGGER_TRADE_MATCH,\
        ST_TRIGGER_SANPSHOT_FILL, ST_TRIGGER_TIMER, ST_TRIGGER_CYCLE],  "Error "

        if not self._strategy.isRealTimeStatus():
            return

        allData = event.getData()
        klineType = event.getKLineType()
        args = {
            "Status": ST_STATUS_CONTINUES,
            "TriggerType": eventCode,
            "ContractNo": event.getContractNo(),
            "KLineType": klineType,
            "KLineSlice": event.getKLineSlice(),
            "TradeDate": allData["TradeDate"],
            "DateTimeStamp": allData["DateTimeStamp"],
            "TriggerData": allData["Data"]
        }
        
        ## print(args)
        # 判断当前触发类型是否需要触发
        if self.checkTriggerEvent(eventCode):
            self._strategy.setCurTriggerSourceInfo(args)
            context.setCurTriggerSourceInfo(args)
            handle_data(context)
        
        if eventCode == ST_TRIGGER_SANPSHOT_FILL:
            # 计算浮动盈亏
            try:
                self._calcProfitByQuote(event)
            except Exception as e:
                errText = traceback.format_exc()
                self.logger.error(f"即时行情计算浮动盈亏出现错误,{errText}")

            # 处理实时阶段止损止盈
            lv1Data = event.getData()["Data"]
            if 4 in lv1Data:
                if klineType not in self._config.getStopWinKtBlack():
                    self._stopWinOrLose(event.getContractNo(), False, lv1Data)
                    self._stopFloatWinLose(event.getContractNo(), False, lv1Data)
            else:
                # 交易所套利无最新价
                comtype = event.getContractNo().split('|')[1]
                if comtype != 'S' and comtype != 'M':
                    self.logger.info(f"即时行情中的字段没有最新价")

            if event.getContractNo() not in self._config.getTriggerContract():
                return
        else:
            pass
        
        self._sendFlushEvent()

    def _sendRealTimeKLine2Client(self, key, data):
        # print("now data is ", data, self._getKLineSlice())
        event = Event({
            "EventCode": EV_ST2EG_UPDATE_KLINEDATA,
            "StrategyId": self._strategy.getStrategyId(),
            "ContractNo": key[0],
            "KLineType":  key[1],
            "KLineSlice": key[2],
            "Data": {
                'Count': 1,
                "Data": [data, ],
            }
        })
        # print("问题1:实盘阶段:", data["KLineIndex"], data["DateTimeStamp"])
        self._strategy.sendEvent2Engine(event)

    def _calcProfitByQuote(self, event):
        #
        contNo = event.getContractNo()
        data = event.getData()
        lv1Data = data["Data"]
        dateTimeStamp = data["DateTimeStamp"]
        #tradeDate = data["TradeDate"]
        tradeDate = self._dataModel.getTradeDate(contNo, dateTimeStamp)
        isLastPriceChanged = data["IsLastPriceChanged"]

        if not isLastPriceChanged:
            return

        priceInfos = {}
        contNo = self._dataModel.getIndexMap(contNo)
        priceInfos[contNo] = {
            "LastPrice": lv1Data[4],
            "HighPrice": lv1Data[4],
            "LowPrice": lv1Data[4],
            "TradeDate": tradeDate,
            "DateTimeStamp" : dateTimeStamp,
            "LastPriceSource": LastPriceFromQuote
        }
        
        #self.logger.debug("_calcProfitByQuote:%s"%priceInfos)
        self._calc.calcProfit([contNo], priceInfos)

    #
    def _stopWinOrLose(self, contractNo, isHis, data):
        stopWinParams = self._config.getStopWinParams(contractNo)
        stopLoseParams = self._config.getStopLoseParams(contractNo)
        
        # 处理止损止盈
        # latestPos = self._calc.getLatestOpenOrder(contractNo)
        latestBuyPos = self._calc.getLatestBuyOpenOrder(contractNo)["Order"]
        latestSellPos = self._calc.getLatestSellOpenOrder(contractNo)["Order"]
        
        # 没有设置止损止盈, 或者没有持仓
        if (not stopLoseParams and not stopWinParams) or (not latestBuyPos and not latestSellPos):
            return

        if isHis:
            highPrice = data["HighPrice"]
            lowPrice  = data["LowPrice"]
        else:
            highPrice = data[4]
            lowPrice  = data[4]

        priceTick = self._dataModel.getPriceTick(contractNo)

        # 止损或者止盈是否触发
        isStopWinBuyTrigger = False
        isStopLoseBuyTrigger = False
        isStopWinSellTrigger = False
        isStopLoseSellTrigger = False
        
        orderStopWinType = otLimit
        orderStopLoseType = otLimit


        # 买方向,价格上涨,需要止盈,价格下跌需要止损
        # 卖方向,价格下跌,需要止盈,价格上涨需要止损
        # self.logger.debug('AAAA:%s,%s,%f'%(latestPos, stopWinParams, curPrice))
        if stopWinParams:
            priceStopWinType = stopWinParams["CoverPosOrderType"]
            if priceStopWinType == 3:
                orderStopWinType = otMarket
                
            if latestBuyPos:
                isStopWinBuyTrigger = highPrice-latestBuyPos["OrderPrice"]-stopWinParams["StopPoint"]*priceTick>-1e-6
            if latestSellPos:
                isStopWinSellTrigger = latestSellPos["OrderPrice"]-lowPrice-stopWinParams["StopPoint"]*priceTick>-1e-6
        if stopLoseParams:
            priceStopLoseType = stopLoseParams["CoverPosOrderType"]
            if priceStopLoseType == 3:
                orderStopLoseType = otMarket

            if latestBuyPos:
                isStopLoseBuyTrigger = latestBuyPos["OrderPrice"]-lowPrice-stopLoseParams["StopPoint"]*priceTick>-1e-6
            if latestSellPos:
                isStopLoseSellTrigger = highPrice-latestSellPos["OrderPrice"]-stopLoseParams["StopPoint"]*priceTick>-1e-6

        # 日志记录
        if isStopWinBuyTrigger:
            if self._strategy.isHisStatus():
                self.logger.info(f"{contractNo} 的历史k线触发了BuyPos止盈, High: {highPrice}, Low: {lowPrice}")
            else:
                self.logger.info(f"{contractNo} 的即时行情触发了BuyPos止盈, High: {highPrice}, Low: {lowPrice}")
        if isStopLoseBuyTrigger:
            if self._strategy.isHisStatus():
                self.logger.info(f"{contractNo} 的历史k线触发了BuyPos止损, High: {highPrice}, Low: {lowPrice}")
            else:
                self.logger.info(f"{contractNo} 的即时行情触发了BuyPos止损, High: {highPrice}, Low: {lowPrice}")       
        if isStopWinSellTrigger:
            if self._strategy.isHisStatus():
                self.logger.info(f"{contractNo} 的历史k线触发了SellPos止盈, High: {highPrice}, Low: {lowPrice}")
            else:
                self.logger.info(f"{contractNo} 的即时行情触发了SellPos止盈, High: {highPrice}, Low: {lowPrice}")
        if isStopLoseSellTrigger:
            if self._strategy.isHisStatus():
                self.logger.info(f"{contractNo} 的历史k线触发了SellPos止损, High: {highPrice}, Low: {lowPrice}")
            else:
                self.logger.info(f"{contractNo} 的即时行情触发了SellPos止损, High: {highPrice}, Low: {lowPrice}")
    
        allPos = self._calc.getPositionInfo(contractNo)

        if isStopWinBuyTrigger:
            coverPosPrice = self.getCoverPosPrice(isHis, data, latestBuyPos["OrderPrice"], stopWinParams, priceTick, dSell)
            self._dataModel.setSell('', contractNo, allPos["TotalBuy"], coverPosPrice, oCoverA, orderStopWinType)
            self._dataModel.setPlotText(coverPosPrice, "止盈", 0x2F4F4F, True, 0)
        if isStopWinSellTrigger:
            coverPosPrice = self.getCoverPosPrice(isHis, data, latestSellPos["OrderPrice"], stopWinParams, priceTick, dBuy)
            self._dataModel.setBuyToCover('', contractNo, allPos["TotalSell"], coverPosPrice, oCoverA, orderStopWinType)
            self._dataModel.setPlotText(coverPosPrice, "止盈", 0x2F4F4F, True, 0)
        if isStopLoseBuyTrigger:
            coverPosPrice = self.getCoverPosPrice(isHis, data, latestBuyPos["OrderPrice"], stopLoseParams, priceTick, dSell)
            self._dataModel.setSell('', contractNo, allPos["TotalBuy"], coverPosPrice, oCoverA, orderStopLoseType)
            self._dataModel.setPlotText(coverPosPrice, "止损", 0x2F4F4F, True, 0)
        if isStopLoseSellTrigger:
            coverPosPrice = self.getCoverPosPrice(isHis, data, latestSellPos["OrderPrice"], stopLoseParams, priceTick, dBuy)
            self._dataModel.setBuyToCover('', contractNo, allPos["TotalSell"], coverPosPrice, oCoverA, orderStopLoseType)
            self._dataModel.setPlotText(coverPosPrice, "止损", 0x2F4F4F, True, 0)
            
    def getCoverPosPrice(self, isHis, data, orderPrice, stopParams, priceTick, direction):
        # price 应该根据coverPosOrderType调整, 0: 最新价,1:对盘价,2:挂单价,3:市价,4:停板价
        price = None
        priceType = stopParams["CoverPosOrderType"]
        addPoint  = stopParams["AddPoint"]
        stopPoint = stopParams["StopPoint"]
        stopType  = stopParams["StopType"]
        # 历史阶段
        if isHis:
            price = orderPrice

            # 根据买卖方向,超价点数调整价格
            if direction == dBuy:
                price = price + addPoint*priceTick
                
                # 根据止损止盈类型调整价格
                if stopType == '0':
                    price = price - stopPoint*priceTick
                elif stopType == '1':
                    price = price + stopPoint*priceTick
            elif direction == dSell:
                price = price - addPoint*priceTick
                
                # 根据止损止盈类型调整价格
                if stopType == '0':
                    price = price + stopPoint*priceTick
                elif stopType == '1':
                    price = price - stopPoint*priceTick
            
            # 根据最高最低价调整价格
            price = min(price, data["HighPrice"])
            price = max(price, data["LowPrice"])
            
        # 即时阶段
        else:
            # 默认取最新价
            key = 4
            # 买方向
            if direction == dBuy:
                # 最新价
                if priceType == 0:
                    pass
                # 对盘价
                elif priceType == 1:
                    if 19 in data:
                        key = 19
                # 挂单价
                elif priceType == 2:
                    if 17 in data:
                        key = 17
                # 市价
                elif priceType == 3:
                    return 0
                # 停板价
                elif priceType == 4:
                    if 9 in data:
                        return data[9]
                else:
                    return data[key]
                # 买价根据超价点数调整(买加卖减)
                price = data[key] + addPoint*priceTick
                # 买价根据涨停价调整
                if 9 in data:
                    price = min(price, data[9])
                
            elif direction == dSell:
                # 最新价
                if priceType == 0:
                    pass
                # 对盘价
                elif priceType == 1:
                    if 17 in data:
                        key = 17
                # 挂单价
                elif priceType == 2:
                    if 19 in data:
                        key = 19
                # 市价
                elif priceType == 3:
                    return 0
                # 停板价
                elif priceType == 4:
                    if 10 in data:
                        return data[10]
                else:
                    return data[key]
                # 卖价根据超价点数调整(买加卖减)
                price = data[key] - addPoint*priceTick
                # 卖价根据跌停价调整
                if 10 in data:
                    price = max(price, data[10])
                                
        return price

    # 相对于
    #isMonitorTrigger = {}
    def _stopFloatWinLose(self, contractNo, isHis, data):
        floatStopParams = self._config.getFloatStopPoint(contractNo)
        #latestPos = self._calc.getLatestOpenOrder(contractNo)
        latestBuyPos = self._calc.getLatestBuyOpenOrder(contractNo)["Order"]
        latestSellPos = self._calc.getLatestSellOpenOrder(contractNo)["Order"]
    
        if not floatStopParams or (not latestBuyPos and not latestSellPos):
            return
            
        if isHis:
            highPrice = data["HighPrice"]
            lowPrice  = data["LowPrice"]
            lastPrice = data["LastPrice"]
        else:
            highPrice = data[4]
            lowPrice  = data[4]
            lastPrice = data[4]
            

        priceTick = self._dataModel.getPriceTick(contractNo)

        highBuyPrice  = self._calc.getLatestBuyOpenOrder(contractNo)["LastEntryHPrice"]
        lowBuyPrice   = self._calc.getLatestBuyOpenOrder(contractNo)["LastEntryLPrice"]  
        highSellPrice = self._calc.getLatestSellOpenOrder(contractNo)["LastEntryHPrice"]
        lowSellPrice  = self._calc.getLatestSellOpenOrder(contractNo)["LastEntryLPrice"]

        highBuyPrice = max(highBuyPrice, highPrice)
        lowBuyPrice  = min(lowBuyPrice, lowPrice)
        highSellPrice = max(highSellPrice, highPrice)
        lowSellPrice  = min(lowSellPrice, lowPrice)

        '''
        allPos = self._calc.getPositionInfo(contractNo)

        buyPrice  = latestBuyPos.get("OrderPrice", 0)
        sellPrice = latestSellPos.get("OrderPrice", 0)
        self.logger.debug("BuyPos:%d, SellPos:%d, buyPrice:%f, sellPrice:%f, highBuyPrice:%f, lowBuyPrice:%f, highSellPrice:%f, lowSellPrice:%f, priceTick:%f, buyPos:%s, sellPos:%s" %(allPos["TotalBuy"], allPos["TotalSell"], buyPrice, sellPrice, highBuyPrice, lowBuyPrice, highSellPrice, lowSellPrice, priceTick, latestBuyPos, latestSellPos))
        '''
        
        isFloatStopBuyTrigger  = False
        isFloatStopSellTrigger = False

        # 监控最高点
        # 买方向,达到最高点,开始监控止损,下跌到止损点时触发
        # 卖方向,达到最低点,开始监控止损,上涨到止损点时触发
        if latestBuyPos:
            if highBuyPrice - latestBuyPos["OrderPrice"] >= floatStopParams["StartPoint"]*priceTick:
                if highBuyPrice - lowPrice >= floatStopParams["StopPoint"]*priceTick:
                    isFloatStopBuyTrigger = True
         
        if latestSellPos:
            if latestSellPos["OrderPrice"] - lowSellPrice >= floatStopParams["StartPoint"]*priceTick:
                if highPrice - lowSellPrice >= floatStopParams["StopPoint"]*priceTick:
                    isFloatStopSellTrigger = True
    
        if not isFloatStopBuyTrigger and not isFloatStopSellTrigger:
            return
            
        allPos = self._calc.getPositionInfo(contractNo)
        
        if isFloatStopBuyTrigger:
            if self._strategy.isHisStatus():
                self.logger.info(f"{contractNo} 的历史k线触发了BuyPos浮动止损止盈, High: {highPrice}, Low: {lowPrice}")
            else:
                self.logger.info(f"{contractNo} 的即时行情触发了BuyPos浮动止损止盈, High: {highPrice}, Low: {lowPrice}")
        
        if isFloatStopSellTrigger:
            if self._strategy.isHisStatus():
                self.logger.info(f"{contractNo} 的历史k线触发了SellPos浮动止损止盈, High: {highPrice}, Low: {lowPrice}")
            else:
                self.logger.info(f"{contractNo} 的即时行情触发了SellPos浮动止损止盈, High: {highPrice}, Low: {lowPrice}")      

        priceFloatStopType = floatStopParams["CoverPosOrderType"]
        orderFloatStopType = otLimit
        if priceFloatStopType == 3:
            orderFloatStopType = otMarket

        if isFloatStopBuyTrigger:
            coverPosPrice = self.getCoverPosPrice(isHis, data, highBuyPrice, floatStopParams, priceTick, dSell)
            self._dataModel.setSell('', contractNo, allPos["TotalBuy"], coverPosPrice, oCoverA, orderFloatStopType)
            self._dataModel.setPlotText(coverPosPrice, "跟踪止损", 0x2F4F4F, True, 0)
            
        if isFloatStopSellTrigger:
            coverPosPrice = self.getCoverPosPrice(isHis, data, lowSellPrice, floatStopParams, priceTick, dBuy)
            self._dataModel.setBuyToCover('', contractNo, allPos["TotalSell"], coverPosPrice, oCoverA, orderFloatStopType)
            self._dataModel.setPlotText(coverPosPrice, "跟踪止损", 0x2F4F4F, True, 0)
            

    def _sendSyncTriggerEvent(self, contractNo):
        syncTriggerInfo = self._triggerMgr.getSyncTriggerInfo(contractNo)

        # 发送填充k线事件
        for record, kLine in syncTriggerInfo.items():
            if record[1] ==0:
                continue
            event = Event({
                "EventCode": ST_TRIGGER_FILL_DATA,
                "ContractNo": record[0],
                "KLineType": record[1],
                "KLineSlice": record[2],
                "Data": {
                    "Data": kLine,
                    "Status": ST_STATUS_CONTINUES
                }
            })
            self._strategy.sendTriggerQueue(event)

        for record, kLine in syncTriggerInfo.items():
            if record == (contractNo, 0, 0) or record not in self._config.getKLineTriggerInfoSimple():
                pass
            else:
                event = Event({
                    "EventCode": ST_TRIGGER_KLINE,
                    "ContractNo": record[0],
                    "KLineType": record[1],
                    "KLineSlice": record[2],
                    "Data": {
                        "Data": kLine,
                        "DateTimeStamp": kLine["DateTimeStamp"],
                        "TradeDate": kLine["TradeDate"],
                    }
                })
                self._strategy.sendTriggerQueue(event)