'''
myBT = engine(portforlio,money,startDate,endDate,turnover,benchmark,price)
myBT.backTesting()
myBT.getDailyResult()
'''
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.CRITICAL) # higher level, less showing 
from ..util.kySDK import * 
from ..API import marketValueAPI
from ..API import CY_FinacialRatio_RP
from .position import Position
from .account import Account
from datetime import datetime
import os
import time
import sys

class Engine():
    myAPI = marketValueAPI.API('BTMarketTrade')
    myAPI.init()
    myAPI2 = CY_FinacialRatio_RP.API('BTEMmidd2')
    myAPI2.init()
    def __init__(self,strategyID,portforlio,initialCash,startDate,endDate,benchmark,tradingHabit,criteriaDict):

        if endDate < startDate:
            raise ValueError('endDate cannot be smaller than startDate')
        
        if isinstance(portforlio,str) and portforlio in ['HS300','ZZ500','SZ50']:
            if portforlio == 'HS300':
                portforlio = 'SHSE.000300'
            elif portforlio == 'ZZ500':
                portforlio = 'SHSE.000905'
            elif portforlio == 'SZ50':
                portforlio = 'SHSE.000016'
            
            self.portforlio = [stockCode[-6:] for stockCode in getIndexPoolFromKy(portforlio)] #['SHSE.600000','SHSE.000002'] 

        elif isinstance(portforlio,str) and portforlio == 'all':
            
            self.portforlio = self.myAPI.getAllStockCode(startDate)

        elif isinstance(portforlio,list):
            self.portforlio = portforlio

        print('len(self.portforlio)',len(self.portforlio))

        self.strategyID = strategyID
        self.initialCash = initialCash
        self.startDate = startDate
        self.endDate = endDate
        self.benchmark = benchmark  
        self.tradingHabit = tradingHabit  
        self.BTRecords = dict()     
        self.IndexRecords = None  
        self.tradingDayHistory = list() # will help get the previous trading day
        self.criteriaDict = criteriaDict
        self.turnover =


    def screenTrueStocks(self,stockList,criteriaDict,spotDate):
        #
        TrueStocks = set()
        for key,values in criteriaDict.items():
            #print(stockList,spotDate,key,values[0],values[1],values[2])
            temp = self.myAPI2.validateStockList(stockList,spotDate,key,values[0],values[1],values[2])
            for k,v in temp.items():
                if v == True: 
                    TrueStocks.add(k)
        return list(TrueStocks)

    @staticmethod
    def printRecord(mydict):
        #print('mydict: ',mydict)
        for x in mydict:
            print(x,mydict[x])


    def keepRecord(self,oneDay,positionList,totalValue,cash,positionListValue):
        '''
        put daily BT record into Json 
        '''
        self.BTRecords[oneDay] = dict()
        self.BTRecords[oneDay]['positionList'] = positionList
        self.BTRecords[oneDay]['totalValue'] = totalValue
        self.BTRecords[oneDay]['cash'] = cash
        self.BTRecords[oneDay]['positionListValue'] = positionListValue
        self.tradingDayHistory.append(oneDay)
        
        if not os.path.exists('./Strategy Records'):
            os.makedirs('./Strategy Records')


        with open('./Strategy Records/strategy_%s.txt'%self.strategyID,'a+') as f:
            f.write('--------' + oneDay + '----------\n')
            f.write('positionList: %s\n'%len(positionList))
            f.write('cash: %s\n'%cash)
            f.write('positionListValue: %s\n'%positionListValue)
            f.write('totalValue: %s\n'%totalValue)
            f.write('\n')


    def getRecord(self,oneDay):
        return self.BTRecords[oneDay]

    def backTesting(self,turnover):
        logger.critical('################################ backtesting ###################################: %s'%(self.strategyID)) 
        myaccount = Account(self.initialCash,'OpenPrice')

        '''
        tradingHabit: 'OpenPrice'|'ClosePrice'|   OpenPrice is prefered

        criteriaDict: {'DeductNetProfitYoY':[None,5,'最近一期']}   #dict(item:[period,upperbound,lowerbound])

        if turnover is int, then turnover for every N days
        if turnover is None, then buy and sell according to specific condition,
        if turnover is flexible, then buy and sell according to specific condition, + turnover days
        '''
        time.sleep(2)
        #print('aaaaaaaaaaaaaaaaaaaaaa','./Strategy Records/strategy_%s.txt'%(self.strategyID))
        try:
            with open('./Strategy Records/strategy_%s.txt'%self.strategyID,'w+') as f:
                f.write('             |' + self.strategyID + '|                \n')
        except :
            e = sys.exc_info()[0]
            print( "<p>Error: %s</p>" % e )

        if turnover == 0:
            turnover == 100000000

        if isinstance(turnover,int) and turnover > 0: # keep turnover
            totalRuningDays = 1
            countDays = 1
            for day in self.myAPI.getTradingDays(self.startDate,self.endDate):
                logger.critical('%s ########## %s ############ totalRuningDays %s'%(self.strategyID,day,totalRuningDays))  
                myaccount.printMe()
                self.keepRecord(day,myaccount.positionList,myaccount.totalValue,myaccount.cash,myaccount.positionListValue) # keep daily BT record 
                
                if len(self.tradingDayHistory) > 1:
                    previousTradingDay = self.tradingDayHistory[-2]
                    previousRecord = self.getRecord(previousTradingDay)
                    previousPositionList = previousRecord['positionList']

                if countDays == 1: 
                    # first day, buy 
                    # screen for qualified stocks
                    trueStocks = self.screenTrueStocks(self.portforlio,self.criteriaDict,day)
                    #print('buy ',trueStocks,'  ' ,len(trueStocks))
                    # update 
                    if len(trueStocks) == 0:
                        print('%s has no data, continue'%day)
                        countDays += 1
                        totalRuningDays += 1
                        continue

                    marketValueOneDay = self.myAPI.getMarketValueOneDay(day,trueStocks)
                    logger.warn(marketValueOneDay)
                    
                    if marketValueOneDay is None: #if marketValueOneDay has no data
                        print('%s has no data, continue'%day)
                        countDays += 1
                        totalRuningDays += 1
                        continue

                    for stock in list(marketValueOneDay):
                        #print('stock.LLimitUpTime',stock.LLimitUpTime,' ',type(stock.LLimitUpTime)) # exclude Tingpai or LimitUp before the market is open
                        if stock.TradeStatus == '0':
                            print('TradeStatus = "0" , 当日交易被禁止，从需买列表中删除之,:%s'%stock.SecuCode)
                            marketValueOneDay.remove(stock)
                        if stock.RiseOrDown == '1' and datetime.strptime(stock.LLimitUpTime[11:],'%H:%M:%S') < datetime(1900, 1, 1, 9, 30,0):
                            print('9:30以前涨停，无法买入，从需买列表中删除之')
                            marketValueOneDay.remove(stock)
                        if len(self.tradingDayHistory) > 1: 
                            if stock.SecuCode in [x.SecuCode for x in previousPositionList]:
                                print('昨日的positionList中已有该票，无法买入，从需买列表中删除之: %s'%stock.SecuCode)
                                if stock.SecuCode in [x.SecuCode for x in marketValueOneDay]:
                                    marketValueOneDay.remove(stock)
                        
                    #print('len(marketValueOneDay)  ',len(marketValueOneDay))        
                    for stock in marketValueOneDay: # buy the stock that is available to buy  
                        #print('backtesting check: %s',stock.SecuCode)
                        #print('len(marketValueOneDay)  ',len(marketValueOneDay))
                        #print('myaccount.cash  ',myaccount.cash)
                        myaccount.buy(stock,myaccount.initialCash*0.95/len(marketValueOneDay))  # need to be very careful, make sure the initialCash won't change constantly 
                        

                    countDays += 1
                    totalRuningDays += 1
  
                elif countDays < turnover:
                    temp = myaccount.held(day)
                    countDays += 1
                    totalRuningDays += 1
                    if isinstance(temp,str):
                        continue 

                elif countDays == turnover:
                    if myaccount.isempty():
                        #print('empty nothing to see in this account')
                        countDays = 1
                        totalRuningDays += 1
                        continue

                    positionStockName = [x.SecuCode for x in myaccount.positionList] # get positions in account and sell

                    positionStockOneDay = self.myAPI.getMarketValueOneDay(day,positionStockName) # list of stock


                    logger.warn(positionStockOneDay)
                    
                    if marketValueOneDay is None: #if marketValueOneDay has no data
                        print('%s has no data, will sell the next day'%day)
                        totalRuningDays += 1
                        continue

                    for stock in list(positionStockOneDay):
                        #print(stock.TradeStatus)
                        #print('stock.LLimitUpTime',stock.LLimitUpTime,' ',type(stock.LLimitUpTime)) # exclude Tingpai or LimitUp before the market is open
                        if stock.TradeStatus == '0':
                            print('TradeStatus = "0" , 当日交易被禁止，从卖出列表中删除之,: %s'%stock.SecuCode)
                            positionStockOneDay.remove(stock)

                        if stock.RiseOrDown == '-1' and datetime.strptime(stock.LLimitUpTime[11:],'%H:%M:%S') < datetime(1900, 1, 1, 9, 30,0):
                            print('9:30以前跌停，无法卖出，从卖出列表中删除之') # 还没有考虑之后打开跌停的情况
                            positionStockOneDay.remove(stock)


                    myaccount.sellAvailableForSell([x.SecuCode for x in positionStockOneDay])
                    countDays = 1
                    totalRuningDays += 1
                    

        elif turnover is None:
            if stock.SecuCode == '600000' and round(stock.OpenPrice)%2 == 1:
                pass
        elif turnover == 'flexible':
            pass
            
        self.getIndexResult()
        logger.critical('################################ backtesting completed ###################################: %s'%(self.strategyID)) 
        
    def getIndexResult(self):
        indexList = self.myAPI.getIndexMarketValue(self.startDate,self.endDate,self.benchmark)
        
        #print('indexList',indexList)
        #print(indexList[-1].TradeDate,' ', indexList[-1].ClosePrice,' ', indexList[0].TradeDate,' ', indexList[0].ClosePrice)
        #zhangfu1 = (indexList[-1].ClosePrice - indexList[0].ClosePrice ) / indexList[0].ClosePrice   
        #print('zhangfu of index', zhangfu1)
        self.IndexRecords = indexList

        #with open('result.txt','a+') as f:
        #    f.write(str(zhangfu1) + str(datetime.now()) + '\n')
            
        #self.printRecord(self.BTRecords)
    

    
        