# encoding: UTF-8

"""

"""


from ctaBase import *
from ctaTemplate import CtaTemplate
from vtGateway import VtOrderData, VtTradeData,VtAccountData
from vtConstant import *
from ctaSetting import *
from vtFunction import mailhelper

import datetime
import pandas as pd
import talib,math,os,time
import numpy as np
import cPickle as pickle

#BERTREND
UP  = "UP"
DOWN = "DOWN"
DING = "DING"
DI = "DI"
FENXINGDISTANCE = 4#两分型（counter）之间距离大于等于4
PICKLE_PATH = os.getcwd() + os.sep + "FirstPointPickleStrategy.pk"


########################################################################
class FirstPointPickleStrategy(CtaTemplate):
    """关于利用macd的时间力度辅助判断第一买卖点的交易策略"""
    className = 'FirstPointStrategy'
    author = u'薛耕'

    # 策略参数
    initDays = 40           # 初始化数据所用的天数

    # 策略变量
    tickPrice = None
    bar = None                  # K线对象
    barMinute = EMPTY_STRING    # K线当前的分钟
    bufferSize = 100                    # 需要缓存的数据的大小
    bufferCount = 0                     # 目前已经缓存了的数据的计数

    orderList = []                      # 保存委托代码的列表

    # 参数列表，保存了参数的名称
    paramList = ['name',
                 'className',
                 'author',
                 'vtSymbol']    

    # 变量列表，保存了变量的名称
    varList = ['inited',
               'trading',
               'pos']  

    #----------------------------------------------------------------------
    def __init__(self, ctaEngine, setting):
         
        """===========================申明变量=============================="""
        super(FirstPointPickleStrategy, self).__init__(ctaEngine, setting)
       
        ###测试变量
        self.start_time = datetime.datetime(2016,11,24)
        self.end_time = None
        self.vAvailable = 1000000.0#虚拟可用资金
        
        
        ###合约变量
        self.vRate = 0.6#保证金比例
        self.jiesuan_time = datetime.time(15,0,0)
        self.pingcang_time = datetime.time(14,55,0)
        
        ###指标变量
        self.closes = []
        self.macd_len = 34
        self.macd_area_buffer = []#分型出现就清空，添加入backup，macd数据加入areas
        self.macd_area_backup = []#替换不清空
        self.macd_areas = []#第一个分型不去比较红蓝柱子面积
        self.macd_duration = []#此次分型对应的macd除以此次分型形成时间的值
        self.bolling_len = 5
        self.bolling_fenxing = []#布尔值数列，表示分型是否处在超强区域
        self.powerful_area = False#是否在超强区域
        
        self.last_fenxing_datetime = None
        self.last_fenxing_type = None
        
        ###交易变量
        self.last_fenxing_datetime2 = None
        self.volume = 1#新增分型购买手数，每替代一次手数加一
        self.real_volume = 1#根据基数来确定买入量
        self.max_replace_counts = 4
        self.ding_signal = False
        self.di_signal = False
        self.risk_rate = 0.8
        self.account = VtAccountData()
        self.mh = mailhelper()
       
        ###分型变量
        self.fenxing = None
        self.lastFenxing = None
        self.fenxingCounter = 0
        self.lastFenxingCounter = 0
        
        ###k线变量
        self.newBar = None
        self.firstBar = None
        self.middleBar = None
        self.lastBar = None
        self.barCounter = 0
        self.barTrend = None#改变为常量
        self.bars = []
        
        ###包含变量
        self.hasBaohan = False
        
        ###统计变量
        self.fenxingTuples = []
      
        try:
            obj = pickle.load(open(PICKLE_PATH, "rb"))
            self.__dict__ = obj.__dict__
            del obj
        except:
            pass
                
    
    #----------------------------------------------------------------------
    def __del__(self):
        """ 当解释器关闭的时候将对象数据存到pickle中 """
        
        pickle.dump(self, open(PICKLE_PATH, "wb"), True)
        del self
        
    #----------------------------------------------------------------------
    def onInit(self):
        """初始化策略（必须由用户继承实现）"""
        self.writeCtaLog(u'%s策略初始化' %self.name)
        # 载入历史数据，并采用回放计算的方式初始化策略数值
        
        initData = self.loadBar(self.initDays)
        for bar in initData:
            self.onBar(bar)

        self.putEvent()

    #----------------------------------------------------------------------
    def onStart(self):
        """启动策略（必须由用户继承实现）"""
        self.writeCtaLog(u'%s策略启动' %self.name)
        self.putEvent()

    #----------------------------------------------------------------------
    def onStop(self):
        """停止策略（必须由用户继承实现）"""
        self.writeCtaLog(u'%s策略停止' %self.name)
        self.putEvent()

    #----------------------------------------------------------------------
    
    def onAccount(self, account):
        """获取账户信息"""
        self.account = account  #VtAccountData
    
    #----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送（必须由用户继承实现）"""
        # 计算K线
        tickMinute = tick.datetime.minute

        if tickMinute != self.barMinute:    
            if self.bar:
                self.onBar(self.bar)

            bar = CtaBarData()              
            bar.vtSymbol = tick.vtSymbol
            bar.symbol = tick.symbol
            bar.exchange = tick.exchange

            bar.open = tick.lastPrice
            bar.high = tick.lastPrice
            bar.low = tick.lastPrice
            bar.close = tick.lastPrice
            self.tickPrice = tick.lastPrice

            bar.date = tick.date
            bar.time = tick.time
            bar.datetime = tick.datetime    # K线的时间设为第一个Tick的时间

            self.bar = bar                  # 这种写法为了减少一层访问，加快速度
            self.barMinute = tickMinute     # 更新当前的分钟
        else:                               # 否则继续累加新的K线
            bar = self.bar                  # 写法同样为了加快速度

            bar.high = max(bar.high, tick.lastPrice)
            bar.low = min(bar.low, tick.lastPrice)
            bar.close = tick.lastPrice

    #----------------------------------------------------------------------
    def onBar(self, bar):
        """收到Bar推送（必须由用户继承实现）"""
        # 撤销之前发出的尚未成交的委托（包括限价单和停止单）
        """这个好，撤销之前的单"""
        for orderID in self.orderList:
            self.cancelOrder(orderID)
        self.orderList = []

       
        
        self.newBar = bar.__dict__
        self.process_baohan()
        self.process_fenxing()
        self.process_indextools()
        self.process_tradesignal()
        self.process_sendmail()
        
        ###如果是下午收盘前五分钟，则平仓所有,测试时请注释这段
        now_time = datetime.datetime.now().time()
        if now_time > self.pingcang_time and now_time < self.jiesuan_time:
            self.process_jiesuan()
        else:
            self.process_ordertrigger()
            
        #self.process_ordertrigger()   
        self.newBar = None
        
        # 存对象，防止中断
      
        pickle.dump(self, open(PICKLE_PATH, "wb"), True)
        # 发出状态更新事件
        self.putEvent()
    
    def process_baohan(self):
        """===========================包含函数=================================="""
        """
        存在包含条件的k线进行包含处理
        """
        if self.lastFenxing and\
            self.newBar['low'] >= self.lastBar['low'] and\
            self.newBar['high'] <= self.lastBar['high']:#存在包含关系;分型之后才需要判断包含
                
            self.hasBaohan = True
        
        else:
            self.hasBaohan = False
            self.firstBar = self.middleBar
            self.middleBar = self.lastBar
            self.lastBar = self.newBar
            self.barCounter += 1
            self.bars.append(self.newBar)        
    
    def process_fenxing(self):
        """===========================分型函数=============================="""
        """
        用于确定顶分型和底分型
        """
        if self.firstBar == None:return
        #顶分型
        if (self.middleBar['high'] > self.firstBar['high'] and self.middleBar['high'] > self.lastBar['high']
            and self.middleBar['low'] > self.firstBar['low'] and self.middleBar['low'] > self.firstBar['low']):
            self.fenxing = (self.middleBar['datetime'],self.middleBar['high'],DING)
            self.fenxingCounter = self.barCounter
            
            try:#如果上个分型不存在，直接加入分型队列
                assert self.lastFenxing
            except AssertionError:
                self.fenxingTuples.append(self.fenxing)
                self.lastFenxing = self.fenxing
                self.lastFenxingCounter = self.fenxingCounter
            
            if self.lastFenxing[2] == DING:#如果上一个分型也是顶分型的话，保留高的那个
                if self.fenxing[1] >= self.lastFenxing[1]:#如果后来居上
                    self.fenxingTuples[-1] = self.fenxing
                    self.lastFenxing = self.fenxing
                    self.lastFenxingCounter = self.fenxingCounter
                else:#后者顶分型力度不够
                    pass
                    
            else:#上一个分型是底分型,无共用K线，就加入列表，确认分型
                if self.fenxingCounter - self.lastFenxingCounter >= FENXINGDISTANCE :#无共用k线
                    self.fenxingTuples.append(self.fenxing)
                    self.lastFenxing = self.fenxing
                    self.lastFenxingCounter = self.fenxingCounter
            
        #底分型
        elif (self.middleBar['high'] < self.firstBar['high'] and self.middleBar['high'] < self.lastBar['high']
            and self.middleBar['low'] < self.firstBar['low'] and self.middleBar['low'] < self.firstBar['low']):
            
            self.fenxing = (self.middleBar['datetime'],self.middleBar['low'],DI)
            self.fenxingCounter = self.barCounter
            
            try:#如果上个分型不存在，直接加入分型队列
                assert self.lastFenxing
            except AssertionError:
                self.fenxingTuples.append(self.fenxing)
                self.lastFenxing = self.fenxing
                self.lastFenxingCounter = self.fenxingCounter
            
            
            if self.lastFenxing[2] == DI:#如果上一个分型也是底分型的话，保留低的那个
                
                if self.fenxing[1] <= self.lastFenxing[1]:#如果后来居下
                    self.fenxingTuples[-1] = self.fenxing
                    self.lastFenxing = self.fenxing
                    self.lastFenxingCounter = self.fenxingCounter
                
                else:#后者底分型力度不够
                    pass
            else:#上一个分型是顶分型,无共用K线，就加入列表，确认分型
                if self.fenxingCounter - self.lastFenxingCounter >= FENXINGDISTANCE :#无共用k线
                    self.fenxingTuples.append(self.fenxing)
                    self.lastFenxing = self.fenxing
                    self.lastFenxingCounter = self.fenxingCounter
        #无分型
        else:
            pass
        
    
    def process_indextools(self):
        
        """======================更新辅助指标=================="""
        
        ###更新closes
        close = self.newBar['close']
        self.closes.append(close)
        if len(self.closes) < self.macd_len:return#如果收盘价列表不够用于计算，则继续添加
        if len(self.fenxingTuples)<=1:return#如果两个分型都没有，则继续等待分型
        now_fenxing = self.fenxingTuples[-1]
        ###判断分型是否更新
        ###未更新
        if self.last_fenxing_datetime == now_fenxing[0]:
            
            self.macd_area_buffer.append(talib.MACD(np.array(self.closes))[-1][-1])#buffer添加一个
            
            
        ###更新了
        else:
            ###替代前分型
            if self.last_fenxing_type == now_fenxing[2]:
                
                #macd处理
                self.macd_area_buffer.append(talib.MACD(np.array(self.closes))[-1][-1])#buffer添加一个
                self.macd_area_backup.extend(self.macd_area_buffer)#backup加入buffer
                macd_area = sum(self.macd_area_backup)#计算
                self.macd_areas[-1] = (now_fenxing[0],macd_area)#替换最后一个红蓝柱面积
                self.macd_duration[-1] = (now_fenxing[0],macd_area/(self.fenxingTuples[-1][0]-self.fenxingTuples[-2][0]).total_seconds())
                self.macd_area_buffer = []#清空buffer
                
                #bolling处理
                ceil = talib.BBANDS(np.array(self.closes))[0][-2]
                floor = talib.BBANDS(np.array(self.closes))[-1][-2]
                self.powerful_area = ((now_fenxing[1] > ceil) | (now_fenxing[1] < floor))
                self.bolling_fenxing[-1] = (now_fenxing[0],self.powerful_area)
                
                #判断变量
                self.last_fenxing_datetime = now_fenxing[0]
                
            ###增加新的分型    
            else:
                
                #macd处理
                self.macd_area_backup = []#清空backup
                self.macd_area_buffer.append(talib.MACD(np.array(self.closes))[-1][-1])#buffer添加一个
                self.macd_area_backup.extend(self.macd_area_buffer)#backup加入buffer
                macd_area = sum(self.macd_area_backup)
                self.macd_areas.append((now_fenxing[0],macd_area))#增加一个红蓝柱面积
                self.macd_duration.append((now_fenxing[0],macd_area/(self.fenxingTuples[-1][0]-self.fenxingTuples[-2][0]).total_seconds()))
                self.macd_area_buffer = []#清空buffer
                
                #bolling处理
                ceil = talib.BBANDS(np.array(self.closes))[0][-2]
                floor = talib.BBANDS(np.array(self.closes))[-1][-2]
                self.powerful_area = ((now_fenxing[1] > ceil) | (now_fenxing[1] < floor))
                self.bolling_fenxing.append((now_fenxing[0],self.powerful_area))
                
                #判断变量
                self.last_fenxing_datetime = now_fenxing[0]
                self.last_fenxing_type = now_fenxing[2]
    
    def process_tradesignal(self):
        
        """======================发出交易信号=================="""
        
        #有三个分型即可判断指标，因为这是第一买卖点策略
        if len(self.fenxingTuples) <= 4:return #如果三个分型都没有，则继续等待分型
        now_fenxing = self.fenxingTuples[-1]
        
        ###判断分型是否更新
        ###未更新
        if self.last_fenxing_datetime2 == now_fenxing[0]:
            return
            
        ###更新了
        else:
            ###不管替代前分型还是新增分型,满足条件的话，变一次买一次
            now_duration = self.macd_duration[-1][-1]
            last_duration = self.macd_duration[-3][-1]
            macd_beichi = abs(now_duration) < abs(last_duration)
            
            #对于这个分型是否是第一卖点的判断
            if now_fenxing[-1] == DING:
                qianding = self.fenxingTuples[-3]
                new_high = now_fenxing[1] > qianding[1]#创出新高
                if macd_beichi and new_high:
                    self.ding_signal = True
                    
            #对于这个分型是否是第二买点的判断
            elif now_fenxing[-1] == DI:
                qiandi = self.fenxingTuples[-3]
                new_low = now_fenxing[1] < qiandi[1]#创出新低
                if macd_beichi and new_low:
                    self.di_signal = True
                        
                            
            self.last_fenxing_datetime2 = now_fenxing[0]     
    
    def process_sendmail(self):
        
        """======================发出交易信息给邮箱=================="""
        if not (self.di_signal | self.ding_signal):
            return
        now_fenxing = self.fenxingTuples[-1]
        qian_fenxing = self.fenxingTuples[-3]
        now_duration = self.macd_duration[-1]
        last_duration = self.macd_duration[-3]
        if self.di_signal:
            
            s1 = "现在分型：" + str(now_fenxing)
            s2 = "上个分型：" + str(qian_fenxing)
            s3 = "macd力度：" + str(now_duration)
            s4 = "上次macd力度：" + str(last_duration)
            
            #if self.trading:
            self.mh.send_mail(['621181828@qq.com'],"出现底——做多信号",'\n'.join([s1,s2,s3,s4]))
            
        elif self.ding_signal:
            
            s1 = "现在分型：" + str(now_fenxing)
            s2 = "上个分型：" + str(qian_fenxing)
            s3 = "macd力度：" + str(now_duration)
            s4 = "上次macd力度：" + str(last_duration)
            
            #if self.trading:
            self.mh.send_mail(['621181828@qq.com'],"出现顶——做空信号",'\n'.join([s1,s2,s3,s4]))
            
    def process_ordertrigger(self):
        
        """======================发出交易Order=================="""
        
        if self.di_signal:
            
            if self.pos < 0:
                
                orderID = self.cover(self.newBar['close'] + 1, abs(self.pos), stop=True)#平仓所有空头
                self.orderList.append(orderID)
                self.volume = 1
                
            if self.volume > self.max_replace_counts:#最多买4+3+2+1手
                
                self.di_signal = False
                return
                
            self.buy(self.newBar['close']+1, self.real_volume)
            self.volume += 1
            
        elif self.ding_signal:
            
            if self.pos > 0:
                
                orderID = self.sell(self.newBar['close'] - 1, abs(self.pos), stop=True)#平仓所有多头
                self.orderList.append(orderID)
                self.volume = 1
            
            if self.volume > self.max_replace_counts:#最多做空4+3+2+1手
                
                self.ding_signal = False
                return
                
            self.short(self.newBar['close']-1, self.real_volume)
            self.volume += 1
            
        self.ding_signal = False
        self.di_signal = False
    
    #----------------------------------------------------------------------
    def process_jiesuan(self):
        
        """======================在结算前五分钟内平仓所有持仓=================="""
        
        if self.pos < 0:
                
            orderID = self.cover(self.newBar['close'] + 1, abs(self.pos), stop=True)#平仓所有空头
            self.orderList.append(orderID)
            self.volume = 1
            
        if self.pos > 0:
                
            orderID = self.sell(self.newBar['close'] - 1, abs(self.pos), stop=True)#平仓所有多头
            self.orderList.append(orderID)
            self.volume = 1
            
        self.ding_signal = False
        self.di_signal = False
        
    #----------------------------------------------------------------------
    def onOrder(self, order):
        """收到委托变化推送（必须由用户继承实现）"""
        pass

    #----------------------------------------------------------------------
    def onTrade(self, trade):
        
        #price = self.closes[-1]
        #if self.volume == self.max_replace_counts+1:
        #    self.volume = 1
        #fenmu = sum(range(self.volume,self.max_replace_counts+1))
        #part = self.volume/float(fenmu)
        #self.real_volume = int(math.ceil(part*((self.account.available*self.risk_rate)/(price*self.vRate))))
        
        
        
        
        #开多
        if trade.direction == DIRECTION_LONG and trade.offset == OFFSET_OPEN:
            self.vAvailable -= trade.price * trade.volume * self.vRate
        #平多
        elif trade.direction == DIRECTION_SHORT and OFFSET_CLOSE in trade.offset:
            self.vAvailable += trade.price * trade.volume * self.vRate
        #开空
        elif trade.direction == DIRECTION_SHORT and trade.offset == OFFSET_OPEN:
            self.vAvailable += trade.price * trade.volume * self.vRate
        #平空
        elif trade.direction == DIRECTION_LONG and OFFSET_CLOSE in trade.offset :
            self.vAvailable -= trade.price * trade.volume * self.vRate
        
        #print self.vAvailable
        
        price = self.closes[-1]
        if self.volume == self.max_replace_counts+1:self.volume = 1
        fenmu = sum(range(self.volume,self.max_replace_counts+1))
        part = self.volume/float(fenmu)
        self.real_volume = int(math.ceil(part*((self.vAvailable*self.risk_rate)/(price*self.vRate))))
        
        #print trade.offset,trade.direction,trade.volume,trade.price
      


















