# encoding: UTF-8

'''
本文件中实现了CTA策略引擎，针对CTA类型的策略，抽象简化了部分底层接口的功能。

关于平今和平昨规则：
1. 普通的平仓OFFSET_CLOSET等于平昨OFFSET_CLOSEYESTERDAY
2. 只有上期所的品种需要考虑平今和平昨的区别
3. 当上期所的期货有今仓时，调用Sell和Cover会使用OFFSET_CLOSETODAY，否则
   会使用OFFSET_CLOSE
4. 以上设计意味着如果Sell和Cover的数量超过今日持仓量时，会导致出错（即用户
   希望通过一个指令同时平今和平昨）
5. 采用以上设计的原因是考虑到vn.trader的用户主要是对TB、MC和金字塔类的平台
   感到功能不足的用户（即希望更高频的交易），交易策略不应该出现4中所述的情况
6. 对于想要实现4中所述情况的用户，需要实现一个策略信号引擎和交易委托引擎分开
   的定制化统结构（没错，得自己写）

 Modified by IncenseLee（李来佳）
 1、增加单一策略里，多个vtSymbol的配置。
 2、修改loadSetting和SaveSetting
'''

print('load ctaEngine.py')
import json
import os
import traceback
from collections import OrderedDict
from datetime import datetime, timedelta
import re

from vnpy.trader.vtEvent import *
from vnpy.trader.vtConstant import *
from vnpy.trader.vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData, VtSignalData
from vnpy.trader.app.ctaStrategy.ctaBase import *
from vnpy.trader.setup_logger import setup_logger
from vnpy.trader.vtFunction import todayDate, getJsonPath

# 加载 strategy目录下所有的策略
from vnpy.trader.app.ctaStrategy.strategy import STRATEGY_CLASS

########################################################################
class CtaEngine(object):
    """CTA策略引擎"""

    # 策略配置文件
    settingFileName = 'CTA_setting.json'
    settingfilePath = getJsonPath(settingFileName, __file__)

    #----------------------------------------------------------------------
    def __init__(self, mainEngine, eventEngine):
        """Constructor"""
        self.mainEngine = mainEngine
        self.eventEngine = eventEngine

        # 当前日期
        self.today = todayDate()

        # 保存策略实例的字典
        # key为策略名称，value为策略实例，注意策略名称不允许重复
        self.strategyDict = {}

        # 保存策略设置的字典
        # key为策略名称，value为策略设置，注意策略名称不允许重复
        self.settingDict = {}

        # 保存vtSymbol和策略实例映射的字典（用于推送tick数据）
        # 由于可能多个strategy交易同一个vtSymbol，因此key为vtSymbol
        # value为包含所有相关strategy对象的list
        self.tickStrategyDict = {}

        # 保存vtOrderID和strategy对象映射的字典（用于推送order和trade数据）
        # key为vtOrderID，value为strategy对象
        self.orderStrategyDict = {}

        # 本地停止单编号计数
        self.stopOrderCount = 0
        # stopOrderID = STOPORDERPREFIX + str(stopOrderCount)

        # 本地停止单字典
        # key为stopOrderID，value为stopOrder对象
        self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除
        self.workingStopOrderDict = {}      # 停止单撤销后会从本字典中删除

        # 持仓缓存字典
        # key为vtSymbol，value为PositionBuffer对象
        self.posBufferDict = {}

        # 成交号集合，用来过滤已经收到过的成交推送
        self.tradeSet = set()

        # 引擎类型为实盘
        self.engineType = ENGINETYPE_TRADING

        # tick缓存
        self.tickDict = {}

        # 未能订阅的symbols
        self.pendingSubcribeSymbols = {}

        # 注册事件监听
        self.registerEvent()

        self.logger = None

        self.createLogger()

    # ----------------------------------------------------------------------
    def sendOrder(self, vtSymbol, orderType, price, volume, strategy,priceType=PRICETYPE_LIMITPRICE):
        """发单"""
        contract = self.mainEngine.getContract(vtSymbol)

        req = VtOrderReq()
        req.symbol = contract.symbol        # 合约代码
        req.exchange = contract.exchange    # 交易所
        req.price = self.roundToPriceTick(contract.priceTick, price)         # 价格
        req.volume = volume                 # 数量

        if strategy:
            req.productClass = strategy.productClass
            req.currency = strategy.currency
        else:
            req.productClass = ''
            req.currency = ''

        # 设计为CTA引擎发出的委托只允许使用限价单
        # modified by incense, 通过参数传递
        req.priceType = priceType     # 价格类型

        # CTA委托类型映射
        if orderType == CTAORDER_BUY:
            req.direction = DIRECTION_LONG      # 合约方向
            req.offset = OFFSET_OPEN            # 开/平
        elif orderType == CTAORDER_SELL:
            req.direction = DIRECTION_SHORT

            # 只有上期所才要考虑平今平昨
            if contract.exchange != EXCHANGE_SHFE:
                req.offset = OFFSET_CLOSE
            else:
                # 获取持仓缓存数据
                posBuffer = self.posBufferDict.get(vtSymbol, None)
                # 如果获取持仓缓存失败，则默认平昨
                if not posBuffer:
                    req.offset = OFFSET_CLOSE

                # modified by IncenseLee 2016/11/08，改为优先平昨仓
                elif posBuffer.longYd :
                    req.offset = OFFSET_CLOSE
                else:
                    req.offset = OFFSET_CLOSETODAY

                # 否则如果有多头今仓，则使用平今
                #elif posBuffer.longToday:
                #    req.offset= OFFSET_CLOSETODAY
                # 其他情况使用平昨
                #else:
                #    req.offset = OFFSET_CLOSE

        elif orderType == CTAORDER_SHORT:
            req.direction = DIRECTION_SHORT
            req.offset = OFFSET_OPEN

        elif orderType == CTAORDER_COVER:
            req.direction = DIRECTION_LONG

            # 只有上期所才要考虑平今平昨
            if contract.exchange != EXCHANGE_SHFE:
                req.offset = OFFSET_CLOSE
            else:
                # 获取持仓缓存数据
                posBuffer = self.posBufferDict.get(vtSymbol, None)
                # 如果获取持仓缓存失败，则默认平昨
                if not posBuffer:
                    req.offset = OFFSET_CLOSE

                #modified by IncenseLee 2016/11/08，改为优先平昨仓
                elif posBuffer.shortYd:
                    req.offset = OFFSET_CLOSE
                else:
                    req.offset = OFFSET_CLOSETODAY

                # 否则如果有空头今仓，则使用平今
                #elif posBuffer.shortToday:
                #    req.offset= OFFSET_CLOSETODAY
                # 其他情况使用平昨
                #else:
                #    req.offset = OFFSET_CLOSE

        vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName)    # 发单

        if strategy:
            self.orderStrategyDict[vtOrderID] = strategy        # 保存vtOrderID和策略的映射关系

            self.writeCtaLog(u'策略%s发送委托，%s, %s，%s，%s@%s'
                         %(strategy.name, vtSymbol, req.offset, req.direction, volume, price))
        else:
            self.writeCtaLog(u'%s发送委托，%s, %s，%s，%s@%s'
                             % ('CtaEngine', vtSymbol, req.offset, req.direction, volume, price))
        return vtOrderID

    # ----------------------------------------------------------------------
    def cancelOrder(self, vtOrderID):
        """撤单"""
        # 查询报单对象
        # 1.调用主引擎接口，查询委托单对象
        order = self.mainEngine.getOrder(vtOrderID)

        # 如果查询成功
        if order:
            # 2.检查是否报单（委托单）还有效，只有有效时才发出撤单指令
            orderFinished = (order.status == STATUS_ALLTRADED or order.status == STATUS_CANCELLED)
            if not orderFinished:
                req = VtCancelOrderReq()
                req.symbol = order.symbol
                req.exchange = order.exchange
                req.frontID = order.frontID
                req.sessionID = order.sessionID
                req.orderID = order.orderID
                self.mainEngine.cancelOrder(req, order.gatewayName)
            else:
                if order.status == STATUS_ALLTRADED:
                    self.writeCtaLog(u'委托单({0}已执行，无法撤销'.format(vtOrderID))
                if order.status == STATUS_CANCELLED:
                    self.writeCtaLog(u'委托单({0}已撤销，无法再次撤销'.format(vtOrderID))
        # 查询不成功
        else:
            self.writeCtaLog(u'委托单({0}不存在'.format(vtOrderID))

    # ----------------------------------------------------------------------
    def cancelOrders(self, symbol, offset=EMPTY_STRING):
        """撤销所有单"""
        # Symbol参数:指定合约的撤单；
        # OFFSET参数:指定Offset的撤单,缺省不填写时，为所有

        l = self.mainEngine.getAllWorkingOrders()

        self.writeCtaLog(u'从所有订单{0}中撤销{1}'.format(len(l), symbol))

        for order in l:

            if symbol == EMPTY_STRING:
                symbolCond = True
            else:
                symbolCond = order.symbol == symbol

            if offset == EMPTY_STRING:
                offsetCond = True
            else:
                offsetCond = order.offset == offset

            if symbolCond and offsetCond:
                req = VtCancelOrderReq()
                req.symbol = order.symbol
                req.exchange = order.exchange
                req.frontID = order.frontID
                req.sessionID = order.sessionID
                req.orderID = order.orderID
                self.writeCtaLog(u'撤单:{0}/{1},{2}{3}手'
                                 .format(order.symbol, order.orderID, order.offset, order.totalVolume-order.tradedVolume))
                self.mainEngine.cancelOrder(req, order.gatewayName)

    # ----------------------------------------------------------------------
    def sendStopOrder(self, vtSymbol, orderType, price, volume, strategy):
        """发停止单（本地实现）"""

        # 1.生成本地停止单ID
        self.stopOrderCount += 1
        stopOrderID = STOPORDERPREFIX + str(self.stopOrderCount)

        # 2.创建停止单对象
        so = StopOrder()
        so.vtSymbol = vtSymbol          # 代码
        so.orderType = orderType        # 停止单类型
        so.price = price                # 价格
        so.volume = volume              # 数量
        so.strategy = strategy          # 来源策略
        so.stopOrderID = stopOrderID    # Id
        so.status = STOPORDER_WAITING   # 状态

        if orderType == CTAORDER_BUY:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SHORT:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_CLOSE

        # 保存stopOrder对象到字典中
        self.stopOrderDict[stopOrderID] = so            # 字典中不会删除
        self.workingStopOrderDict[stopOrderID] = so     # 字典中会删除

        self.writeCtaLog(u'发停止单成功，'
                         u'Id:{0},Symbol:{1},Type:{2},Price:{3},Volume:{4}'
                         u'.'.format(stopOrderID, vtSymbol, orderType, price, volume))

        return stopOrderID

    # ----------------------------------------------------------------------
    def cancelStopOrder(self, stopOrderID):
        """撤销停止单
        Incense Li modified 20160124：
        增加返回True 和 False
        """
        # 1.检查停止单是否存在
        if stopOrderID in self.workingStopOrderDict:
            so = self.workingStopOrderDict[stopOrderID]
            so.status = STOPORDER_CANCELLED                 # STOPORDER_WAITING =》STOPORDER_CANCELLED
            del self.workingStopOrderDict[stopOrderID]      # 删除
            self.writeCtaLog(u'撤销停止单:{0}成功.'.format(stopOrderID))
            return True
        else:
            self.writeCtaLog(u'撤销停止单:{0}失败，不存在Id.'.format(stopOrderID))
            return False

    # ----------------------------------------------------------------------
    def processStopOrder(self, tick):
        """收到行情后处理本地停止单（检查是否要立即发出）"""
        vtSymbol = tick.vtSymbol

        # 1.首先检查是否有策略交易该合约
        if vtSymbol in self.tickStrategyDict:
            # 2.遍历等待中的停止单，检查是否会被触发
            for so in self.workingStopOrderDict.values():
                if so.vtSymbol == vtSymbol:
                    # 3. 触发标识判断
                    longTriggered = so.direction == DIRECTION_LONG and tick.lastPrice >= so.price        # 多头停止单被触发
                    shortTriggered = so.direction == DIRECTION_SHORT and tick.lastPrice <= so.price     # 空头停止单被触发

                    # 4.触发处理
                    if longTriggered or shortTriggered:

                        # 5.设定价格，买入和卖出分别以涨停跌停价发单（模拟市价单）
                        if so.direction == DIRECTION_LONG:
                            price = tick.upperLimit
                        else:
                            price = tick.lowerLimit

                        # 6.更新停止单状态，触发
                        so.status = STOPORDER_TRIGGERED

                        # 7.发单
                        self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy)

                        # 8.删除停止单
                        del self.workingStopOrderDict[so.stopOrderID]

    # ----------------------------------------------------------------------
    def procecssTickEvent(self, event):
        """处理行情推送事件"""

        # 1. 获取事件的Tick数据
        tick = event.dict_['data']

        # 移除待订阅的合约清单
        if tick.vtSymbol in self.pendingSubcribeSymbols:
            self.writeCtaLog(u'已成功订阅{0}，从待订阅清单中移除'.format(tick.vtSymbol))
            del self.pendingSubcribeSymbols[tick.vtSymbol]

        # 缓存最新tick
        self.tickDict[tick.vtSymbol] = tick

        # 2.收到tick行情后，优先处理本地停止单（检查是否要立即发出）
        self.processStopOrder(tick)

        # 3.推送tick到对应的策略对象进行处理
        if tick.vtSymbol in self.tickStrategyDict:

            # 4.将vtTickData数据转化为ctaTickData
            ctaTick = CtaTickData()
            d = ctaTick.__dict__
            for key in d.keys():

                if key != 'datetime':
                    d[key] = tick.__getattribute__(key)
            # 添加datetime字段
            ctaTick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f')

            # 5.添加datetime字段
            if ctaTick.datetime.hour >= 20:
                dt = datetime.now()
                today = dt.strftime('%Y%m%d')
                ctaTick.datetime = datetime.strptime(' '.join([today, tick.time]), '%Y%m%d %H:%M:%S.%f')
                ctaTick.date = today

            # 逐个推送到策略实例中
            l = self.tickStrategyDict[tick.vtSymbol]
            for strategy in l:
                self.callStrategyFunc(strategy, strategy.onTick, ctaTick)

    # ----------------------------------------------------------------------
    def processOrderEvent(self, event):
        """处理委托推送事件"""
        # 1.获取事件的Order数据
        order = event.dict_['data']

        # order.vtOrderID 在gateway中，已经格式化为 gatewayName.vtOrderID

        # 2.判断order是否在映射字典中
        if order.vtOrderID in self.orderStrategyDict:
            # 3.提取对应的策略
            strategy = self.orderStrategyDict[order.vtOrderID]
            # 4.触发策略的委托推送事件方法
            strategy.onOrder(order)

    # ----------------------------------------------------------------------
    def processTradeEvent(self, event):
        """处理成交推送事件"""

        # 1.获取事件的Trade数据
        trade = event.dict_['data']

        # 过滤已经收到过的成交回报
        if trade.vtTradeID in self.tradeSet:
            return
        self.tradeSet.add(trade.vtTradeID)

        # 将成交推送到策略对象中
        if trade.vtOrderID in self.orderStrategyDict:

            # 3.提取对应的策略
            strategy = self.orderStrategyDict[trade.vtOrderID]

            # 计算策略持仓 ( canceled by IncenseLee )
            #if trade.direction == DIRECTION_LONG:
            #    strategy.pos += trade.volume
            #else:
            #    strategy.pos -= trade.volume

            self.callStrategyFunc(strategy, strategy.onTrade, trade)

        # 更新持仓缓存数据
        if trade.vtSymbol in self.tickStrategyDict:
            posBuffer = self.posBufferDict.get(trade.vtSymbol, None)
            if not posBuffer:
                posBuffer = PositionBuffer()
                posBuffer.vtSymbol = trade.vtSymbol
                self.posBufferDict[trade.vtSymbol] = posBuffer
            posBuffer.updateTradeData(trade)

    #----------------------------------------------------------------------
    def processPositionEvent(self, event):
        """处理持仓推送"""
        pos = event.dict_['data']

        # 更新持仓缓存数据
        if pos.vtSymbol in self.tickStrategyDict:
            posBuffer = self.posBufferDict.get(pos.vtSymbol, None)
            if not posBuffer:
                posBuffer = PositionBuffer()
                posBuffer.vtSymbol = pos.vtSymbol
                self.posBufferDict[pos.vtSymbol] = posBuffer
            posBuffer.updatePositionData(pos)

    #----------------------------------------------------------------------
    def registerEvent(self):
        """注册事件监听"""

        # 注册行情数据推送（Tick数据到达）的响应事件
        self.eventEngine.register(EVENT_TICK, self.procecssTickEvent)

        # 注册订单推送的响应事件
        self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)

        # 注册成交推送（交易）的响应时间
        self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)

        # 注册持仓更新事件
        self.eventEngine.register(EVENT_POSITION, self.processPositionEvent)

        # 账号更新事件（借用账号更新事件，来检查是否有未订阅的合约信息）
        self.eventEngine.register(EVENT_ACCOUNT, self.checkUnsubscribedSymbols)

        # 注册定时器事件
        self.eventEngine.register(EVENT_TIMER, self.processTimerEvent)

        # 注册强制止损事件
        self.eventEngine.register(EVENT_ACCOUNT_LOSS, self.processAccoutLossEvent)

    def processAccoutLossEvent(self,event):
        """处理止损时间"""
        balance = event.dict_['data']
        self.writeCtaLog(u'净值{0}低于止损线，执行强制止损'.format(balance))
        self.mainEngine.writeLog(u'净值{0}低于止损线，执行强制止损'.format(balance))

        self.cancelOrders(symbol=EMPTY_STRING)

        for posBuffer in self.posBufferDict.values():

            if posBuffer.shortYd > 0:
                self.writeCtaLog(u'{0}合约持有昨空单{1}手，强平'.format(posBuffer.vtSymbol,posBuffer.shortYd))
                tick = self.tickDict.get(posBuffer.vtSymbol, None)

                if not tick:
                    self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
                    continue

                self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_COVER, price=tick.upperLimit, volume=posBuffer.shortYd,strategy=None)

            if posBuffer.shortToday > 0:
                self.writeCtaLog(u'{0}合约持有今空单{1}手，强平'.format(posBuffer.vtSymbol,posBuffer.shortToday))
                tick = self.tickDict.get(posBuffer.vtSymbol, None)

                if not tick:
                    self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
                    continue

                self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_COVER, price=tick.upperLimit, volume=posBuffer.shortToday,strategy=None)

            if posBuffer.longYd > 0:
                self.writeCtaLog(u'{0}合约持有昨多单{1}手，强平'.format(posBuffer.vtSymbol,posBuffer.longYd))
                tick = self.tickDict.get(posBuffer.vtSymbol, None)

                if not tick:
                    self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
                    continue

                self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_SELL, price=tick.lowerLimit, volume=posBuffer.longYd,strategy=None)

            if posBuffer.longToday > 0:
                self.writeCtaLog(u'{0}合约持有今多单{1}手，强平'.format(posBuffer.vtSymbol,posBuffer.longToday))
                tick = self.tickDict.get(posBuffer.vtSymbol, None)

                if not tick:
                    self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
                    continue

                self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_SELL, price=tick.lowerLimit, volume=posBuffer.longToday,strategy=None)

    def processTimerEvent(self, event):
        """定时器事件"""

        # 触发每个策略的定时接口
        for strategy in self.strategyDict.values():
            strategy.onTimer()

    # ----------------------------------------------------------------------
    def insertData(self, dbName, collectionName, data):
        """插入数据到数据库（这里的data可以是CtaTickData或者CtaBarData）"""
        self.mainEngine.dbInsert(dbName, collectionName, data.__dict__)

    # ----------------------------------------------------------------------
    def loadBar(self, dbName, collectionName, days):
        """从数据库中读取Bar数据，startDate是datetime对象"""
        startDate = self.today - timedelta(days)

        d = {'datetime':{'$gte':startDate}}
        barData = self.mainEngine.dbQuery(dbName, collectionName, d)

        l = []
        for d in barData:
                bar = CtaBarData()
                bar.__dict__ = d
                l.append(bar)

        return l

    # ----------------------------------------------------------------------

    def loadTick(self, dbName, collectionName, days):
        """从数据库中读取Tick数据，startDate是datetime对象"""
        startDate = self.today - timedelta(days)

        d = {'datetime':{'$gte':startDate}}
        tickData = self.mainEngine.dbQuery(dbName, collectionName, d)

        l = []
        for d in tickData:
                tick = CtaTickData()
                tick.__dict__ = d
                l.append(tick)

        return l

    # ----------------------------------------------------------------------
    # 日志相关
    def writeCtaLog(self, content):
        """快速发出CTA模块日志事件"""
        log = VtLogData()
        log.logContent = content
        event = Event(type_=EVENT_CTA_LOG)
        event.dict_['data'] = log
        self.eventEngine.put(event)

        # 写入本地log日志
        if self.logger:
            self.logger.info(content)
        else:
            self.createLogger()

    def createLogger(self):
        """
        创建日志记录
        :return:
        """
        currentFolder = os.path.abspath(os.path.join(os.getcwd(), 'logs'))
        if os.path.isdir(currentFolder):
            # 如果工作目录下，存在data子目录，就使用data子目录
            path = currentFolder
        else:
            # 否则，使用缺省保存目录 vnpy/trader/app/ctaStrategy/data
            path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'logs'))

        filename = os.path.abspath(os.path.join(path, 'ctaEngine'))

        print( u'create logger:{}'.format(filename))
        self.logger = setup_logger(filename=filename, name='ctaEngine', debug=True)

    def writeCtaError(self,content):
        """快速发出CTA模块错误日志事件"""
        self.mainEngine.writeError(content)

    def writeCtaWarning(self, content):
        """快速发出CTA模块告警日志事件"""
        self.mainEngine.writeWarning(content)

    def writeCtaNotification(self, content):
        """快速发出CTA模块通知事件"""
        self.mainEngine.writeNotification(content)

    def writeCtaCritical(self,content):
        """快速发出CTA模块异常日志事件"""
        self.mainEngine.writeCritical(content)

    def sendCtaSignal(self,source, symbol, direction, price,level):
        """发出交易信号"""
        s = VtSignalData()
        s.source = source
        s.symbol = symbol
        s.direction = direction
        s.price = price
        s.level = level
        event = Event(type_=EVENT_SIGNAL)
        event.dict_['data'] = s
        self.eventEngine.put(event)

    # ----------------------------------------------------------------------
    # 订阅合约相关
    def subscribe(self, strategy, symbol):
        """订阅合约，不成功时，加入到待订阅列表"""
        contract = self.mainEngine.getContract(symbol)

        if contract:
            # 4.构造订阅请求包
            req = VtSubscribeReq()
            req.symbol = contract.symbol
            req.exchange = contract.exchange

            # 对于IB接口订阅行情时所需的货币和产品类型，从策略属性中获取
            req.currency = strategy.currency
            req.productClass = strategy.productClass

            # 5.调用主引擎的订阅接口
            self.mainEngine.subscribe(req, contract.gatewayName)
        else:
            print( u'Warning, can not find {0} in contracts'.format(symbol))
            self.writeCtaLog(u'交易合约{}无法找到，添加到待订阅列表'.format (symbol))
            self.pendingSubcribeSymbols[symbol]=strategy

    def checkUnsubscribedSymbols(self, event):
        """持仓更新信息时，检查未提交的合约"""
        for symbol in self.pendingSubcribeSymbols.keys():
            contract = self.mainEngine.getContract(symbol)
            if contract:
                # 获取合约的缩写号
                s = self.getShortSymbol(symbol)
                #if s == symbol:    # 合约缩写提取失败
                #    continue
                dt = datetime.now()
                # 若为中金所的合约，白天才提交订阅请求
                if s in MARKET_ZJ and not(9 < dt.hour < 16):
                    continue

                self.writeCtaLog(u'重新提交合约{0}订阅请求'.format(symbol))
                strategy = self.pendingSubcribeSymbols[symbol]
                self.subscribe(strategy=strategy, symbol=symbol)

    # ----------------------------------------------------------------------
    # 策略相关（加载/初始化/启动/停止)
    def checkStrategy(self, name):
        """
        检查策略
        :param name:
        :return: NOTRUN：没有运行；RUNING：正常运行；FORCECLOSING:正在关闭;FORCECLOSED:已经关闭
        """
        if name not in self.strategyDict:
            return NOTRUN

        strategy = self.strategyDict[name]
        if hasattr(strategy, 'forceTradingClose'):
            if strategy.forceTradingClose == False:
                return RUNING

            if strategy.trading:
                return FORCECLOSING
            else:
                return FORCECLOSED

        return RUNING

    def loadStrategy(self, setting,is_dispatch=False):
        """
        载入策略
        :param setting: 策略设置参数
        :param is_dispatch: 是否为调度
        :return:
        """
        try:
            name = setting['name']
            className = setting['className']
        except Exception as e:
            self.writeCtaLog(u'载入策略出错：%s' % e)
            self.mainEngine.writeCritical(u'载入策略出错：%s' % e)
            return False

        # 获取策略类
        strategyClass = STRATEGY_CLASS.get(className, None)
        if not strategyClass:
            self.writeCtaLog(u'STRATEGY_CLASS找不到策略类：%s' % className)
            self.mainEngine.writeCritical(u'STRATEGY_CLASS找不到策略类：%s' % className)
            return False

        if is_dispatch:
            # 属于调度
            runing_status = self.checkStrategy(name)
            if name in self.settingDict:
                if runing_status == RUNING:
                    self.writeCtaLog(u'策略{}正常运行，不做加载'.format(name))
                    return False
                elif runing_status in [FORCECLOSING,FORCECLOSED]:
                    try:
                        cur_strategy = self.strategyDict[name]
                        cur_strategy.cancelForceClose()
                        self.settingDict[name] = setting
                        self.writeCtaLog(u'撤销运行中策略{}的强制清仓，恢复运行'.format(name))
                        return False
                    except Exception as ex:
                        self.writeCtaCritical(u'撤销运行中策略{}的强制清仓时异常：{}'.format(name,str(ex)))
                        traceback.print_exc()
                        return False
        else:
            # 防止策略重名
            if name in self.strategyDict:
                self.writeCtaLog(u'策略实例重名：%s' % name)
                return False

            # 检查策略中的forceClose，如果当前日期超过最后平仓日期一周，不再启动。
            if 'forceClose' in setting:
                try:
                    forceCloseDate = datetime.strptime(setting['forceClose'], '%Y-%m-%d')
                    dt = datetime.now()
                    if (dt - forceCloseDate).days > 7:
                        self.writeCtaLog(u'日期超过最后平仓日期,不再启动')
                        return False
                except Exception as ex:
                    self.writeCtaCritical(u'配置文件的强制平仓日期提取异常:{}'.format(str(ex)))
                    traceback.print_exc()

        self.settingDict[name] = setting

        # 1.创建策略对象
        strategy = strategyClass(self, setting)
        self.strategyDict[name] = strategy

        # 2.保存Tick映射关系（symbol <==> Strategy[] )
        # modifid by Incenselee 支持多个Symbol的订阅
        symbols = []
        # 套利合约
        if strategy.vtSymbol.find(' ') != -1:
            # 排除SP SPC SPD
            s = strategy.vtSymbol.split(' ')
            if len(s) > 1:
                arb_symbols = s[1]

                # 只提取leg1合约
                if arb_symbols.find('&') != -1:
                    symbols = arb_symbols.split('&')
            else:
                symbols.append(s[0])
        else:
            symbols = strategy.vtSymbol.split(';')

        # 判断是否有Leg1Symbol,Leg2Symbol 两个合约属性
        if hasattr(strategy, 'Leg1Symbol'):
            if strategy.Leg1Symbol not in symbols:
                symbols.append(strategy.Leg1Symbol)
        if hasattr(strategy, 'Leg2Symbol'):
            if strategy.Leg2Symbol not in symbols:
                symbols.append(strategy.Leg2Symbol)

        for symbol in symbols:
            self.writeCtaLog(u'添加合约{0}与策略的匹配目录'.format(symbol))
            if symbol in self.tickStrategyDict:
                l = self.tickStrategyDict[symbol]
            else:
                l = []
                self.tickStrategyDict[symbol] = l
            l.append(strategy)

            # 3.订阅合约
            self.writeCtaLog(u'向gateway订阅合约{0}'.format(symbol))
            self.subscribe(strategy=strategy, symbol=symbol)

        # 自动初始化
        if 'auto_init' in setting:
            if setting['auto_init'] == True:
                self.writeCtaLog(u'自动初始化策略')
                self.initStrategy(name=name)

        if 'auto_start' in setting:
            if setting['auto_start'] == True:
                self.writeCtaLog(u'自动启动策略')
                self.startStrategy(name=name)
        return True

    def initStrategy(self, name, force = False):
        """初始化策略"""
        if name in self.strategyDict:
            strategy = self.strategyDict[name]

            if not strategy.inited or force == True:
                self.callStrategyFunc(strategy, strategy.onInit,force)
                #strategy.onInit(force=force)
                #strategy.inited = True
            else:
                self.writeCtaLog(u'请勿重复初始化策略实例：%s' %name)
        else:
            self.writeCtaError(u'策略实例不存在：%s' %name)

    def startStrategy(self, name):
        """启动策略"""
        # 1.判断策略名称是否存在字典中
        if name in self.strategyDict:

            # 2.提取策略
            strategy = self.strategyDict[name]

            # 3.判断策略是否运行
            if strategy.inited and not strategy.trading:
                # 4.设置运行状态
                strategy.trading = True
                # 5.启动策略
                self.callStrategyFunc(strategy, strategy.onStart)
        else:
            self.writeCtaError(u'策略实例不存在：%s' %name)

    def stopStrategy(self, name):
        """停止策略运行"""
        # 1.判断策略名称是否存在字典中
        if name in self.strategyDict:
            # 2.提取策略
            strategy = self.strategyDict[name]

            # 3.停止交易
            if strategy.trading:
                # 4.设置交易状态为False
                strategy.trading = False
                # 5.调用策略的停止方法
                self.callStrategyFunc(strategy, strategy.onStop)

                # 6.对该策略发出的所有限价单进行撤单
                for vtOrderID, s in self.orderStrategyDict.items():
                    if s is strategy:
                        self.cancelOrder(vtOrderID)

                # 7.对该策略发出的所有本地停止单撤单
                for stopOrderID, so in self.workingStopOrderDict.items():
                    if so.strategy is strategy:
                        self.cancelStopOrder(stopOrderID)
        else:
            self.writeCtaError(u'策略实例不存在：%s' %name)

    def removeStrategy(self, name):
        """
        移除策略
        :param name:
        :return:
        """
        # 移除策略设置,下次启动不再执行
        if name in self.settingDict:
            self.settingDict.pop(name, None)

        try:
            # 调动策略的强制清仓方法
            self.writeCtaLog(u'调动策略的强制清仓方法')
            strategy = self.strategyDict[name]

            self.writeCtaLog(u'调动策略{}的强制清仓方法'.format(name))
            strategy.forceCloseAllPos()

            # 将运行dict的策略移除.
            self.strategyDict[name] = None
            self.writeCtaLog(u'将运行dict的策略{}关联移除'.format(name))
            self.strategyDict.pop(name, None)

        except Exception as ex:
            self.writeCtaError(u'移除策略异常:{}'.format(str(ex)))
            traceback.print_exc()

    # ----------------------------------------------------------------------
    # 策略配置相关
    def saveSetting(self):
        """保存策略配置"""
        try:
            with open(self.settingfilePath, 'w') as f:
                l = list(self.settingDict.values())
                jsonL = json.dumps(l, indent=4)
                f.write(jsonL)
        except Exception as ex:
            self.writeCtaCritical(u'保存策略配置异常:{}'.format(str(ex)))
            traceback.print_exc()

    def loadSetting(self):
        """
        读取策略配置文件，CTA_setting.json
        逐一运行
        :return:
        """
        try:
            with open(self.settingfilePath) as f:
                l = json.load(f)
                for setting in l:
                    try:
                        self.loadStrategy(setting)
                    except Exception as ex:
                        self.writeCtaCritical(u'加载策略:{}'.format(str(ex)))
                        traceback.print_exc()
            self.loadPosition()
        except Exception as ex:
            self.writeCtaCritical(u'加载策略配置异常:{}'.format(str(ex)))
            traceback.print_exc()

    # ----------------------------------------------------------------------
    # 策略运行监控相关
    def getStrategyVar(self, name):
        """获取策略当前的变量字典"""
        if name in self.strategyDict:
            # 获取策略实例
            strategy = self.strategyDict[name]
            varDict = OrderedDict()

            for key in strategy.varList:
                if hasattr(strategy,key):
                    varDict[key] = strategy.__getattribute__(key)

            return varDict
        else:
            self.writeCtaLog(u'策略实例不存在：' + name)
            return None

    def getStrategyParam(self, name):
        """获取策略的参数字典"""
        if name in self.strategyDict:
            # 获取策略实例
            strategy = self.strategyDict[name]
            paramDict = OrderedDict()

            for key in strategy.paramList:
                if hasattr(strategy, key):
                    paramDict[key] = strategy.__getattribute__(key)

            return paramDict
        else:
            self.writeCtaLog(u'策略实例不存在：' + name)
            return None

    def getStrategySetting(self, name):
        """
        获取策略的配置参数
        :param name: 策略实例名称
        :return:
        """

        if name in self.settingDict:
            return self.settingDict[name]

        else:
            return None
    # ----------------------------------------------------------------------
    def putStrategyEvent(self, name):
        """触发策略状态变化事件（通常用于通知GUI更新）"""
        event = Event(EVENT_CTA_STRATEGY+name)
        d = 'putevent'
        event.dict_['data'] = d
        self.eventEngine.put(event)

        # 触发系统状态更新事件
        self.mainEngine.qryStatus()

    # ----------------------------------------------------------------------
    def callStrategyFunc(self, strategy, func, params=None):
        """调用策略的函数，若触发异常则捕捉"""
        try:
            if params:
                func(params)
            else:
                func()
        except Exception:
            # 停止策略，修改状态为未初始化
            strategy.trading = False
            strategy.inited = False

            # 发出日志
            content = '\n'.join([u'策略%s触发异常已停止' % strategy.name,
                                 traceback.format_exc()])
            self.writeCtaLog(content)
            self.mainEngine.writeCritical(content)

    # ----------------------------------------------------------------------
    # 仓位持久化相关
    def savePosition(self):
        """保存所有策略的持仓情况到数据库"""
        for strategy in self.strategyDict.values():
            flt = {'name': strategy.name,
                   'vtSymbol': strategy.vtSymbol}

            d = {'name': strategy.name,
                 'vtSymbol': strategy.vtSymbol,
                 'pos': strategy.pos}

            self.mainEngine.dbUpdate(POSITION_DB_NAME, strategy.className,
                                     d, flt, True)

            content = '策略%s持仓保存成功' % strategy.name
            self.writeCtaLog(content)

    def loadPosition(self):
        """从数据库载入策略的持仓情况"""
        try:
            for strategy in self.strategyDict.values():
                flt = {'name': strategy.name,
                       'vtSymbol': strategy.vtSymbol}
                posData = self.mainEngine.dbQuery(POSITION_DB_NAME, strategy.className, flt)

                for d in posData:
                    strategy.pos = d['pos']
        except:
            self.writeCtaLog(u'loadPosition Exception from Mongodb')

    # ----------------------------------------------------------------------
    # 公共方法相关
    def roundToPriceTick(self, priceTick, price):
        """取整价格到合约最小价格变动"""
        if not priceTick:
            return price

        newPrice = round(price / priceTick, 0) * priceTick
        return newPrice

    def getShortSymbol(self, symbol):
        """取得合约的短号"""
        # 套利合约
        if symbol.find(' ') != -1:
            # 排除SP SPC SPD
            s = symbol.split(' ')
            if len(s) < 2:
                return symbol
            symbol = s[1]

            # 只提取leg1合约
            if symbol.find('&') != -1:
                s = symbol.split('&')
                if len(s) < 2:
                    return symbol
                symbol = s[0]

        p = re.compile(r"([A-Z]+)[0-9]+", re.I)
        shortSymbol = p.match(symbol)

        if shortSymbol is None:
            self.writeCtaLog(u'{0}不能正则分解'.format(symbol))
            return symbol

        return shortSymbol.group(1)

    # ----------------------------------------------------------------------
    def getAccountInfo(self):
        """获取账号的实时权益、可用资金、仓位比例
        Added by Incenselee
        暂不支持多接口同时运行哦
        """
        return self.mainEngine.getAccountInfo()

    # ---------------------------------------------------------------------
    def saveStrategyData(self):
        """保存策略的数据"""

        # 1.判断策略名称是否存在字典中
        for key in self.strategyDict.keys():

            # 2.提取策略
            strategy = self.strategyDict[key]

            if strategy is None:
                continue

            # 3.判断策略是否运行
            if strategy.inited and strategy.trading:

                try:
                    # 5.保存策略数据
                    strategy.saveData()
                except:
                    traceback.print_exc()

    def clearData(self):
        """清空运行数据"""
        self.tickDict = {}
        self.orderStrategyDict = {}
        self.workingStopOrderDict = {}
        self.posBufferDict = {}
        self.stopOrderDict = {}

    def qryStatus(self):
        """查询cta Engined的运行状态"""

        # 查询最新tick和更新时间
        tick_status_dict = OrderedDict()
        for k,v in self.tickDict.items():
            tick_status_dict[k] = str(v.date + ' ' + v.time)

        # 查询策略运行状态
        strategy_status_dict = OrderedDict()
        for strategy_name in self.strategyDict.keys():
            varList = self.getStrategyVar(strategy_name)
            strategy_status_dict[strategy_name] = varList

        return tick_status_dict, strategy_status_dict

    def qrySize(self,vtSymbol):
        """
        查询合约的大小
        :param vtSymbol:
        :return:
        """
        c = self.mainEngine.getContract(vtSymbol)
        if c is None:
            return 10
        else:
           return c.size

    def qryMarginRate(self,vtSymbol):
        """
        提供给策略查询品种的保证金比率
        :param vtSymbol:
        :return:
        """
        c = self.mainEngine.getContract(vtSymbol)
        if c is None:
            return 0.1
        else:
            # 返回空头/多头保证金费率的最大值
            if c.longMarginRatio > EMPTY_FLOAT and c.shortMarginRatio > EMPTY_FLOAT:
                return max(c.longMarginRatio, c.shortMarginRatio)
            else:
                return 0.1

########################################################################
class PositionBuffer(object):
    """持仓缓存信息（本地维护的持仓数据）"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.vtSymbol = EMPTY_STRING

        # 多头
        self.longPosition = EMPTY_INT
        self.longToday = EMPTY_INT
        self.longYd = EMPTY_INT

        # 空头
        self.shortPosition = EMPTY_INT
        self.shortToday = EMPTY_INT
        self.shortYd = EMPTY_INT

    #----------------------------------------------------------------------
    def updatePositionData(self, pos):
        """更新持仓数据"""
        if pos.direction == DIRECTION_LONG:
            self.longPosition = pos.position
            self.longYd = pos.ydPosition
            self.longToday = self.longPosition - self.longYd
        else:
            self.shortPosition = pos.position
            self.shortYd = pos.ydPosition
            self.shortToday = self.shortPosition - self.shortYd

    #----------------------------------------------------------------------
    def updateTradeData(self, trade):
        """更新成交数据"""
        if trade.direction == DIRECTION_LONG:
            # 多方开仓，则对应多头的持仓和今仓增加
            if trade.offset == OFFSET_OPEN:
                self.longPosition += trade.volume
                self.longToday += trade.volume
            # 多方平今，对应空头的持仓和今仓减少
            elif trade.offset == OFFSET_CLOSETODAY:
                self.shortPosition -= trade.volume
                self.shortToday -= trade.volume
            # 多方平昨，对应空头的持仓和昨仓减少
            else:
                self.shortPosition -= trade.volume
                self.shortYd -= trade.volume
        else:
            # 空头和多头相同
            if trade.offset == OFFSET_OPEN:
                self.shortPosition += trade.volume
                self.shortToday += trade.volume
            elif trade.offset == OFFSET_CLOSETODAY:
                self.longPosition -= trade.volume
                self.longToday -= trade.volume
            else:
                self.longPosition -= trade.volume
                self.longYd -= trade.volume


# -*- coding: utf-8 -*-
"""This module contains custom widgets (composite panels) to be used in the main application frame."""

import wx
import wx.lib.scrolledpanel as scrolled
from math import ceil

import dialog

import config
import fn_path
import fn_gfx

class CompositeGallery(wx.Panel):
    """Custom widget that overlays an "add image" button on top of the WidgetGallery custom widget

        Class Variables:
            btn_size (int): Size of the "add image" button in the overlay

        Args:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab

        Attributes:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab
    """

    btn_size = 25

    def __init__(self, parent, root):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        self.parent = parent
        self.root = root

        # Button overlay and binding
        self.btn_add_image = wx.BitmapButton(self,
                                             bitmap=wx.Bitmap(fn_path.concat_gui('plus.png')),
                                             size=(CompositeGallery.btn_size,) * 2)

        # Image gallery subwidget
        self.pnl_gallery = WidgetGallery(self, self.root)

        # Button overlay binding - Must be after subwidget to bind to
        self.btn_add_image.Bind(wx.EVT_BUTTON, self.pnl_gallery.evt_add_image)
        self.btn_add_image.Bind(wx.EVT_SET_FOCUS, self.evt_btn_no_focus)

        # Main sizer - do not add button so it floats
        szr_main = wx.BoxSizer(wx.VERTICAL)
        szr_main.Add(self.pnl_gallery, proportion=1, flag=wx.EXPAND)
        self.SetSizer(szr_main)

        # Bind button movement to resize
        self.Bind(wx.EVT_SIZE, self.evt_resize)

    def evt_resize(self, event):
        """Move the button overlay when resized

            Args:
                event: A resize event object passed from the resize event
        """

        # Get width and height of the resize and subtract a tuple representing the scrollbar size
        (_w, _h) = event.GetSize() - (16, 0)

        # Move the button that adds more images
        self.btn_add_image.SetPosition((_w - CompositeGallery.btn_size, _h - CompositeGallery.btn_size))

        # Refresh Layout required for unknown reasons - otherwise odd scale behaviour on pnl_gallery
        self.Layout()

    def evt_btn_no_focus(self, event):
        """Prevents focus from being called on the buttons

            Args:
                event: A focus event
        """
        pass

class WidgetGallery(scrolled.ScrolledPanel):
    """Custom scrolled widget to contain a gallery of photos associated with the part of the parent tab

        Class Variables:
            icon_gap (int): Spacing between adjacent icons in sizer
            icon_size (int): Square size intended for the icon for each image

        Args:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab

        Attributes:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab
    """

    icon_gap = 5
    icon_size = 120

    def __init__(self, parent, root):
        """Constructor"""
        super().__init__(parent, style=wx.BORDER_SIMPLE)

        # Variables
        self.parent = parent
        self.root = root

        # Load list of images from database and store the image filenames with extensions
        conn = config.sql_db.connect(config.cfg["db_location"])
        crsr = conn.cursor()
        crsr.execute("SELECT image FROM Images WHERE part_num=(?) AND part_rev=(?);",
                     (self.root.part_num, self.root.part_rev))
        self.image_list = [i[0] for i in crsr.fetchall()]
        conn.close()

        # Create list of raw images
        self.images = [fn_path.concat_img(self.root.part_num, img) for img in self.image_list]

        # Create a grid sizer to contain image icons
        self.nrows, self.ncols = 1, len(self.images)
        self.img_object_list = []
        self.sizer_grid = wx.GridSizer(rows=self.nrows + 1,
                                       cols=self.ncols,
                                       hgap=WidgetGallery.icon_gap,
                                       vgap=WidgetGallery.icon_gap)

        # Add cropped and scaled image icons to the grid sizer
        for r in range(self.nrows):
            for c in range(self.ncols):
                _n = self.ncols * r + c
                _tmp = fn_gfx.crop_square(wx.Image(self.images[_n]), WidgetGallery.icon_size)
                _temp = wx.StaticBitmap(self, bitmap=wx.Bitmap(_tmp))
                _temp.Bind(wx.EVT_LEFT_UP, self.evt_image_click)
                self.img_object_list.append(_temp)
                self.sizer_grid.Add(_temp, wx.EXPAND)

        # Main sizer
        szr_main = wx.BoxSizer(wx.VERTICAL)
        szr_main.Add(self.sizer_grid)
        self.SetSizer(szr_main)

        # Setup the scrolling style and function, wanting only vertical scroll to be available
        self.SetAutoLayout(1)
        self.SetupScrolling()
        self.ShowScrollbars(wx.SHOW_SB_NEVER, wx.SHOW_SB_ALWAYS)
        self.SetWindowStyle(wx.VSCROLL)

        # Bind button movement to resize
        self.Bind(wx.EVT_SIZE, self.evt_resize)

        # Bind layout recalculation to scrolling
        self.Bind(wx.EVT_SCROLLWIN, self.evt_scroll)

    def evt_image_click(self, event):
        """Call up the dialog for when an image is clicked

            Args:
                event: The triggering click event object
        """

        # Load the "image clicked" dialog
        _dlg = dialog.EditImage(self, self.root, self.img_object_list.index(event.GetEventObject()))
        _dlg.ShowModal()
        if _dlg: _dlg.Destroy()

    def evt_add_image(self, event):
        """Call up dialogs to add an image to the database

            Args:
                event: A button event object passed from the button click
        """

        # Open an explorer dialog to select images to import
        with wx.FileDialog(None, "Open",
                           wildcard="Images (*.png;*.jpg;*.bmp;*.gif)|*.png;*.jpg;*.jpeg;*.bmp;*.gif|"
                                    "PNG (*.png)|*.png|"
                                    "JPEG (*.jpg)|*.jpg;*.jpeg|"
                                    "BMP (*.bmp)|*.bmp|"
                                    "GIF (*.gif)|*.gif",
                           style=wx.FD_MULTIPLE | wx.FD_FILE_MUST_EXIST) as file_dialog:

            # Check if the user changed their mind about importing
            if file_dialog.ShowModal() == wx.ID_CANCEL:
                return

            # Make a list of chosen images to add to the database
            selected_files = file_dialog.GetPaths()

        # Proceed loading the file(s) chosen by the user to the "add image" dialog
        _dlg = dialog.AddImage(self, self.root, selected_files)
        _dlg.ShowModal()
        if _dlg: _dlg.Destroy()

    def evt_resize(self, event):
        """Resize the image grid

        Retrieves width and height of the grid panel and adds/removes grid columns/rows to fit panel nicely.

            Args:
                event: A resize event object passed from the resize event
        """

        # Get width and height of the resize
        (_w, _h) = event.GetSize()

        # Calculate the number of columns that fit in the scrolled panel, force a minimum of 1 columns
        _c = max((_w - WidgetGallery.icon_gap) // (WidgetGallery.icon_size + WidgetGallery.icon_gap), 1)

        # Redistribute rows and columns for the grid
        self.sizer_grid.SetCols(_c)
        self.sizer_grid.SetRows(ceil(len(self.image_list)/_c))

    def evt_scroll(self, event):
        """Adds forced recalculation of layout on scroll - as default repainting of frames does not work here

            Args:
                event: Scroll event
        """

        wx.CallAfter(self.Layout)
        event.Skip()

class CompositeNotes(wx.Panel):
    """Custom widget that overlays an "add note" button on top of the WidgetNotes custom widget as well as
    governs the column header behavior

        Class Variables:
            btn_size (int): Size of the "add image" button in the overlay
            col_min (list: int): List of minimum column widths for the column headers

        Args:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab

        Attributes:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab
    """

    btn_size = 25
    col_min = [25, 40, -1]

    def __init__(self, parent, root):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        self.parent = parent
        self.root = root

        # List of text objects in the header
        self.header_list = []

        # Draw button first, as the first object drawn stays on top
        self.btn_add_note = wx.BitmapButton(self,
                                            bitmap=wx.Bitmap(fn_path.concat_gui('plus.png')),
                                            size=(CompositeNotes.btn_size,) * 2)

        # Set up sizer to contain header and scrolled notes
        self.pnl_notes = NotesScrolled(self, self.root)

        # Button overlay binding - Must be after subwidget to bind to
        self.btn_add_note.Bind(wx.EVT_BUTTON, self.pnl_notes.evt_add_note)
        self.btn_add_note.Bind(wx.EVT_SET_FOCUS, self.evt_button_no_focus)

        # Binding for clicking between notes text
        self.pnl_notes.Bind(wx.EVT_LEFT_UP, self.evt_edit_note)

        # Main Sizer
        self.szr_title = wx.BoxSizer(wx.HORIZONTAL)
        self.szr_main = wx.BoxSizer(wx.VERTICAL)
        self.szr_main.Add(self.szr_title, flag=wx.ALL | wx.EXPAND)
        self.szr_main.Add(self.pnl_notes, proportion=1, flag=wx.ALL | wx.EXPAND)

        self.header_list.append(
            wx.StaticText(self,
                          label="Date", style=wx.ALIGN_LEFT))
        self.header_list.append(
            wx.StaticText(self,
                          label="Author", style=wx.ALIGN_LEFT))
        self.header_list.append(wx.StaticText(self, label="Note", style=wx.ALIGN_LEFT))

        self.szr_title.Add(self.header_list[0])
        self.szr_title.Add(self.header_list[1])
        self.szr_title.Add(self.header_list[2], proportion=1)
        #self.szr_title.Add(self.header_list[3])

        # Refresh headers, repopulating self.sizer_title
        self.refresh_headers()

        # Set sizer and resize
        self.SetSizer(self.szr_main)
        self.Layout()

        # Bind button movement to resize
        self.Bind(wx.EVT_SIZE, self.evt_resize)

    def refresh_headers(self):
        """Refresh the column headers to reflect the column widths in the lower scrolled sizer"""

        # Append three 0s in case the notes list is empty. Only the first 3 entries are observed
        column_widths = [*self.pnl_notes.szr_grid.GetColWidths(), 0, 0, 0][:3]

        # Change the size of the first two column headers
        self.header_list[0].SetMinSize((max(column_widths[0], CompositeNotes.col_min[0]) + NotesScrolled.col_gap, -1))
        self.header_list[1].SetMinSize((max(column_widths[1], CompositeNotes.col_min[1]) + NotesScrolled.col_gap, -1))

        # Ensure headers resize properly
        self.Layout()

    def evt_edit_note(self, event):
        """Determine where in the scrolled panel was clicked and pass that to the method handling the dialog

            Args:
                event: A resize event object passed from the click event
        """

        # Mouse positions within the overall panel, corrected for scroll. The math signage is odd, but works out
        pos_panel = self.pnl_notes.ScreenToClient(wx.GetMousePosition())[1]
        pos_scroll = -self.pnl_notes.CalcScrolledPosition(0, 0)[1]

        # Call method from the panel itself to handle dialog popup
        self.pnl_notes.edit_notes((pos_panel + pos_scroll) // 20)

    def evt_resize(self, event):
        """Move the button overlay when resized

            Args:
                event: A resize event object passed from the resize event
        """

        # Get width and height of the resize and subtract a tuple representing the scrollbar size
        (_w, _h) = event.GetSize() - (16, 0)

        # Move the button that adds more images
        self.btn_add_note.SetPosition((_w - CompositeNotes.btn_size, _h - CompositeNotes.btn_size))

        # Refresh Layout required for unknown reasons - otherwise odd scale behaviour on pnl_gallery
        self.Layout()

    def evt_button_no_focus(self, event):
        """Prevents focus from being called on the buttons

            Args:
                event: A focus event
        """
        pass

class NotesScrolled(scrolled.ScrolledPanel):
    """Custom scrolled widget to contain notes associated with the part of the parent tab

        Class Variables:
            row_gap (int): Vertical spacing between rows in grid
            col_gap (int): Horizontal spacing between columns in grid

        Args:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab

        Attributes:
            parent (ref): Reference to the parent wx.object
            root (ref): Reference to the root parts tab
    """

    row_gap = 5
    col_gap = 15

    def __init__(self, parent, root):
        """Constructor"""
        super().__init__(parent, style=wx.ALL | wx.VSCROLL)

        self.parent = parent
        self.root = root

        # List of current notes items in widget; each entry is a list of three wx.objects
        self.notes_list = []

        # Sizer to hold notes entries - Also set as main sizer
        self.szr_grid = wx.FlexGridSizer(3, NotesScrolled.row_gap, NotesScrolled.col_gap)
        self.szr_grid.AddGrowableCol(2)
        self.szr_grid.SetFlexibleDirection(wx.HORIZONTAL)
        self.szr_grid.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_NONE)
        self.SetSizer(self.szr_grid)

        # Load notes into the provided grid sizer
        self.load_notes()

        # Setup the scrolling style and function, wanting only vertical scroll to be available
        self.SetupScrolling()
        self.ShowScrollbars(wx.SHOW_SB_NEVER, wx.SHOW_SB_ALWAYS)
        self.SetWindowStyle(wx.VSCROLL)

        # Not clear as to why this is needed, but without it the scrollbar is missing in some circumstances
        self.SetScrollbars(10, 10, 10, 10)

        # Bind layout recalculation to scrolling
        self.Bind(wx.EVT_SCROLLWIN, self.evt_scroll)

    def load_notes(self):
        """Open SQL database and load notes from table"""

        # Load notes from database
        conn = config.sql_db.connect(config.cfg["db_location"])
        crsr = conn.cursor()
        crsr.execute("SELECT date, author, note FROM Notes WHERE part_num=(?) AND part_rev=(?)",
                     (self.root.part_num, self.root.part_rev))
        _tmp_list = crsr.fetchall()
        conn.close()

        # Sort and remove non-date information from the datetime string
        if _tmp_list:
            _tmp_list = [(a[0][:10],)+a[1:] for a in sorted(_tmp_list, key=lambda x: x[0])]

        # Add the notes to the grid
        for i, note_args in enumerate(_tmp_list):
            self.add_note(*note_args, i)

    def evt_add_note(self, event):
        """Event to trigger the addition of entries to the notes widget

            Args:
                event: A button event object passed from the button click
        """

        _dlg = dialog.AddNote(self, self.root)
        _dlg.ShowModal()
        if _dlg: _dlg.Destroy()

    def add_note(self, timestamp, user, note, id_set=0):
        """Add an entry to the notes widget

            Args:
                self: A reference to the parent wx.object instance
                user: The username that has added the note
                timestamp: The timestamp for when the note was added
                note: The text for the note to be added
        """

        # List of wx.objects to add to the notes widget
        _tmp_item = [wx.StaticText(self, id=id_set, label=timestamp, style=wx.EXPAND),
                     wx.StaticText(self, size=(40, -1), id=id_set, label=user, style=wx.EXPAND),
                     wx.StaticText(self, size=(50, -1), id=id_set, label=note, style=wx.ST_ELLIPSIZE_END)]

        # Binding for the items in the notes widget
        for item in _tmp_item:
            item.Bind(wx.EVT_LEFT_UP, self.evt_edit_notes_trigger)
            self.szr_grid.Add(item, flag=wx.ALL | wx.EXPAND)

        self.notes_list.append(_tmp_item)

        # Refresh both layouts so the scrollbar resets
        self.Layout()
        self.parent.Layout()

    def evt_edit_notes_trigger(self, event):
        """Determine which entry in the scrolled panel was clicked and pass that to the method handling the dialog

            Args:
                event: Click event that triggered this function
        """

        pass
        self.edit_notes(event.GetEventObject().GetId())

    def edit_notes(self, my_index):
        """Method to edit an existing note, based on the provided index in the list

            Args:
                my_index (int): Index for the notes entry you want to edit
        """

        pass
        print(self.notes_list[my_index][0].GetLabel(), self.notes_list[my_index][1].GetLabel(), self.notes_list[my_index][2].GetLabel())
        #dialog = ImageDialog(self.image_list, event.GetEventObject().GetId(), self.parent.part_num, self.parent.part_rev)
        #dialog.ShowModal()
        #dialog.Destroy()

    def evt_scroll(self, event):
        """Adds forced recalculation of layout on scroll - as default repainting of frames does not work here

            Args:
                event: Scroll event
        """

        wx.CallAfter(self.Layout)
        event.Skip()

class CompositeMugshot(wx.Panel):
    """Custom widget that overlays a "show schematic" button on top of the Mugshot widget

            Class Variables:
                mug_size (int): The size of the mugshot photo
                btn_size (int): Size of the "show schematic" button in the overlay

            Args:
                parent (ref): Reference to the parent wx.object
                root (ref): Reference to the root parts tab

            Attributes:
                parent (ref): Reference to the parent wx.object
                root (ref): Reference to the root parts tab
    """

    mug_size = 250
    btn_size = 40

    def __init__(self, parent, root):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        self.parent = parent
        self.root = root

        # Primary part image
        if self.parent.mugshot:
            image = wx.Image(fn_path.concat_img(self.parent.part_num, self.parent.mugshot), wx.BITMAP_TYPE_ANY)
        else:
            image = wx.Image(fn_path.concat_gui('missing_mugshot.png'), wx.BITMAP_TYPE_ANY)

        # Draw button first as first drawn stays on top
        self.button_dwg = wx.BitmapButton(self,
                                          bitmap=wx.Bitmap(fn_path.concat_gui('schematic.png')),
                                          size=(CompositeMugshot.btn_size,) * 2,
                                          pos=(0, CompositeMugshot.mug_size - CompositeMugshot.btn_size))
        self.button_dwg.Bind(wx.EVT_SET_FOCUS, self.event_button_no_focus)
        self.button_dwg.Bind(wx.EVT_BUTTON, self.evt_blueprint)

        self.imageBitmap = wx.StaticBitmap(self, bitmap=wx.Bitmap(fn_gfx.crop_square(image, CompositeMugshot.mug_size)))

        self.sizer_main = wx.BoxSizer(wx.HORIZONTAL)
        self.sizer_main.Add(self.imageBitmap, flag=wx.ALL)

        self.SetSizer(self.sizer_main)
        self.Layout()
        self.Fit()

    def refresh(self, new_image=None):
        """Updates the mugshot to the image hash provided, directs to the no-image if None is provided

            Args:
                new_image (str): Image filename for mugshot to display
        """
        if new_image:
            temp = fn_path.concat_img(self.parent.part_num, new_image)
        else:
            temp = fn_path.concat_gui('missing_mugshot.png')

        # Update image
        self.imageBitmap.SetBitmap(wx.Bitmap(fn_gfx.crop_square(wx.Image(temp), CompositeMugshot.mug_size)))

    def evt_blueprint(self, event):
        """Loads a dialog or opens a program (unsure) showing the production drawing of said part

            Args:
                event: Button click event
        """

        _dlg = wx.RichMessageDialog(self,
                                   caption="This feature is not yet implemented",
                                   message="This feature will load a production drawing of the current part",
                                   style=wx.OK | wx.ICON_INFORMATION)
        _dlg.ShowModal()
        _dlg.Destroy()

    def event_button_no_focus(self, event):
        """Prevents focus from being called on the buttons

            Args:
                event: A focus event
        """
        pass

class CompositeAssemblies(wx.Panel):
    """Custom scrolled widget to contain sub-assembly and super-assembly data associated with the part of the parent tab

            Class Variables:
                btn_size (int): Size of the "add image" button in the overlay

            Args:
                parent (ref): Reference to the parent wx.object
                root (ref): Reference to the root parts tab

            Attributes:
                parent (ref): Reference to the parent wx.object
                root (ref): Reference to the root parts tab
    """

    btn_size = 25

    def __init__(self, parent, root):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        self.parent = parent
        self.root = root

        # Draw Sub-Assembly edit button
        self.btn_sub_edit = wx.BitmapButton(self,
                                            bitmap=wx.Bitmap(fn_path.concat_gui('edit.png')),
                                            size=(CompositeAssemblies.btn_size,) * 2)
        self.btn_sub_edit.Bind(wx.EVT_SET_FOCUS, self.evt_button_no_focus)
        self.btn_sub_edit.Bind(wx.EVT_BUTTON, self.evt_sub_edit)

        # Draw Sub-Assembly help button
        self.btn_sub_help = wx.StaticBitmap(self, bitmap=wx.Bitmap(fn_path.concat_gui('help.png')))
        self.btn_sub_help.Bind(wx.EVT_LEFT_UP, self.evt_sub_help)

        # Draw Super-Assembly edit button
        self.btn_super_edit = wx.BitmapButton(self,
                                              bitmap=wx.Bitmap(fn_path.concat_gui('edit.png')),
                                              size=(CompositeAssemblies.btn_size,) * 2)
        self.btn_super_edit.Bind(wx.EVT_SET_FOCUS, self.evt_button_no_focus)
        self.btn_super_edit.Bind(wx.EVT_BUTTON, self.evt_super_edit)

        # Draw Super-Assembly help button
        self.btn_super_help = wx.StaticBitmap(self, bitmap=wx.Bitmap(fn_path.concat_gui('help.png')))
        self.btn_super_help.Bind(wx.EVT_LEFT_UP, self.evt_super_help)

        # Lists containing sub and super assembly info
        self.wgt_sub_assm = wx.ListBox(self,
                                       choices=[i[0] + " r" + i[1] for i in self.root.helper_wgt_sub],
                                       size=(CompositeMugshot.mug_size//2, -1), style=wx.LB_SINGLE | wx.LB_ALWAYS_SB)
        self.wgt_super_assm = wx.ListBox(self,
                                         choices=[i[0] + " r" + i[1] for i in self.root.helper_wgt_super],
                                         size=(CompositeMugshot.mug_size//2, -1), style=wx.LB_SINGLE | wx.LB_ALWAYS_SB)

        # Assembly list binds
        self.wgt_sub_assm.Bind(wx.EVT_LISTBOX, self.evt_click_sub_assm)
        self.wgt_sub_assm.Bind(wx.EVT_MOTION, self.evt_update_tooltip_sub)
        self.wgt_super_assm.Bind(wx.EVT_LISTBOX, self.evt_click_super_assm)
        self.wgt_super_assm.Bind(wx.EVT_MOTION, self.evt_update_tooltip_super)

        # Assembly and Main Sizers
        self.szr_sub_assm = wx.BoxSizer(wx.VERTICAL)
        self.szr_sub_assm.Add(wx.StaticText(self, label="Sub-Assemblies", style=wx.ALIGN_CENTER), border=5,
                              flag=wx.ALL | wx.EXPAND)
        self.szr_sub_assm.Add(self.wgt_sub_assm, proportion=1, flag=wx.ALL | wx.EXPAND)
        self.szr_super_assm = wx.BoxSizer(wx.VERTICAL)
        self.szr_super_assm.Add(wx.StaticText(self, label="Super-Assemblies", style=wx.ALIGN_CENTER), border=5,
                                flag=wx.ALL | wx.EXPAND)
        self.szr_super_assm.Add(self.wgt_super_assm, proportion=1, flag=wx.ALL | wx.EXPAND)
        self.szr_main = wx.BoxSizer(wx.HORIZONTAL)
        self.szr_main.Add(self.szr_sub_assm, proportion=1, flag=wx.ALL | wx.EXPAND)
        self.szr_main.Add(self.szr_super_assm, proportion=1, flag=wx.ALL | wx.EXPAND)

        # Set main sizer and recalculate the layout
        self.SetSizer(self.szr_main)
        self.Layout()

        # Bind button movement to resize
        self.Bind(wx.EVT_SIZE, self.evt_resize)

    def evt_click_sub_assm(self, event):
        """Open a parts tab based on what entry has been clicked

            Args:
                event: A list box click event object passed from the list box when activated
        """

        # Get the index of the clicked item, and open a new parts tab, based on part_num and part_rev
        _index = event.GetSelection()
        self.root.parent.open_parts_tab(*self.root.helper_wgt_sub[_index], wx.GetKeyState(wx.WXK_SHIFT))

        # Deselect the clicked entry of the list box
        event.GetEventObject().SetSelection(wx.NOT_FOUND)

    def evt_click_super_assm(self, event):
        """Open a parts tab based on what entry has been clicked

            Args:
                event: A list box click event object passed from the list box when activated
        """

        # Get the index of the clicked item, and open a new parts tab, based on part_num and part_rev
        _index = event.GetSelection()
        self.root.parent.open_parts_tab(*self.root.helper_wgt_super[_index], wx.GetKeyState(wx.WXK_SHIFT))

        # Deselect the clicked entry of the list box
        event.GetEventObject().SetSelection(wx.NOT_FOUND)

    def evt_update_tooltip_sub(self, event):
        """Update the tooltip with the name of the sub-assembly entry the mouse is over

            Args:
                event: A mouse movement event object passed from the movement event
        """

        # Calculate the index of the item that is under the mouse pointer
        _item_index = self.wgt_sub_assm.HitTest(event.GetPosition())

        # As long as we have a valid index then update the tooltip, otherwise empty it
        if _item_index != -1:
            _num, _rev = self.root.helper_wgt_sub[_item_index]
            _new_msg = self.root.data_wgt_sub[_num][_rev]
            if self.wgt_sub_assm.GetToolTipText() != _new_msg:
                self.wgt_sub_assm.SetToolTip(_new_msg)
        else:
            self.wgt_sub_assm.SetToolTip("")

    def evt_update_tooltip_super(self, event):
        """Update the tooltip with the name of the super-assembly entry the mouse is over

            Args:
                event: A mouse movement event object passed from the movement event
        """

        # Calculate the index of the item that is under the mouse pointer
        _item_index = self.wgt_super_assm.HitTest(event.GetPosition())

        # As long as we have a valid index then update the tooltip, otherwise empty it
        if _item_index != -1:
            _num, _rev = self.root.helper_wgt_super[_item_index]
            _new_msg = self.root.data_wgt_super[_num][_rev]
            if self.wgt_super_assm.GetToolTipText() != _new_msg:
                self.wgt_super_assm.SetToolTip(_new_msg)
        else:
            self.wgt_super_assm.SetToolTip("")

    def evt_sub_edit(self, event):
        """Show the edit dialog for sub-assemblies

            Args:
                event: A button event object passed from the button click
        """

        _dlg = dialog.EditSubAssemblies(self, self.root)
        _dlg.ShowModal()
        if _dlg: _dlg.Destroy()

    def evt_super_edit(self, event):
        """Show the edit dialog for super-assemblies

            Args:
                event: A button event object passed from the button click
        """

        _dlg = dialog.EditSuperAssemblies(self, self.root)
        _dlg.ShowModal()
        if _dlg: _dlg.Destroy()

    def evt_sub_help(self, event):
        """Show the term definition for sub-assembly

            Args:
                event: A click event object passed from the click event
        """

        _dlg = wx.RichMessageDialog(self,
                                    caption="Help: Term Definition",
                                    message="A sub-assembly in this context refers to components or assemblies that, "
                                            "together, assemble into this part.",
                                    style=wx.OK | wx.ICON_INFORMATION)
        _dlg.ShowModal()
        _dlg.Destroy()

    def evt_super_help(self, event):
        """Show the term definition for super-assembly

            Args:
                event: A click event object passed from the click event
        """

        _dlg = wx.RichMessageDialog(self,
                                    caption="Help: Term Definition",
                                    message="A super-assembly in this context refers to assemblies that contain "
                                            "this part.",
                                    style=wx.OK | wx.ICON_INFORMATION)
        _dlg.ShowModal()
        _dlg.Destroy()

    def evt_resize(self, event):
        """Move the button overlays when re-sized

            Args:
                event: A resize event object passed from the resize event
        """

        _help_shift = 6
        _help_size = 10

        # Get width and height of the resize and subtract a correction tuple
        (_w, _h) = event.GetSize() - (1, 1)

        # Move the buttons that edit the assembly lists, and correct to exclude the scrollbar width (17)
        self.btn_super_edit.SetPosition((_w - CompositeAssemblies.btn_size - 17, _h - CompositeAssemblies.btn_size))
        self.btn_sub_edit.SetPosition((_w // 2 - CompositeAssemblies.btn_size - 17, _h - CompositeAssemblies.btn_size))

        # Move the buttons that display help for the assembly lists
        self.btn_super_help.SetPosition((_w - _help_shift - _help_size // 2,
                                         (self.wgt_sub_assm.GetCharHeight() - _help_size) // 2 + 6))
        self.btn_sub_help.SetPosition((_w // 2 - _help_shift - _help_size // 2,
                                       (self.wgt_sub_assm.GetCharHeight() - _help_size) // 2 + 6))

        # Refresh Layout required for unknown reasons - otherwise odd scale behaviour
        self.Layout()

    def evt_button_no_focus(self, event):
        """Prevents focus from being called on the buttons

            Args:
                event: A focus event
        """
        pass


from core.connection_cursor import cur
from core.utils import db_log
from psycopg2 import sql

def insert_contact(cid, cname, bid):
    """ insert new contact (CALLED AT THE CLIENT SIDE)

    @param cid: contact id (the same as client id in the server side)
    @param cname: contact name
    @param bid: bank account id
    """
    stat=sql.SQL("INSERT INTO contacts (contact_id, contact_name bank_account_id) VALUES ({cid}, {cname}, {bid})").\
        format(cid=sql.Literal(cid), \
               cname=sql.Literal(cname), \
               bid=sql.Literal(bid))
    db_log.debug(stat)
    cur.execute(stat)


#list, set, dictionary comprehensions

my_list=[]
for char in 'hello':
	my_list.append(char)

print(my_list)
simple_dict={
	'a':1,
	'b':2
}

my_list1=[param for param in 'hello']
my_list2={num*2 for num in range(1,100)}
my_list3=[num**2 for num in range(1,100)]
my_list4=[num**2 for num in range(1,100) if num%2==0 ]
#my_list5=[num**2 for num,i in range(1,100) if count(num%i==0)==2 ]
my_dict={key:value**2 for key,value in simple_dict.items()}
my_dict2={num:num*2 for num in [1,2,3]}

print(my_list1)
print(my_list2)
print(my_list3)
print(my_list4)
#print(my_list5)
print(my_dict)
print(my_dict2)

####################################################################
some_list=['a','b','c','b','d','m','n','n']
duplicates={item for item in some_list if some_list.count(item)>1}
print(duplicates)


#!/usr/bin/python3

import tkinter
import time
from sense_hat import SenseHat
import OWMInterface as OWM
import PixelArray as PA
import Frames as frames
import Clock as clock

sense = SenseHat()
sense.clear()
sensorData = {'humidity': sense.get_humidity(), 'temperature': sense.get_temperature(),'pressure': sense.get_pressure()}

if __name__ == "__main__":
        """
        Create a tkinter window and populate it with elements
        One of those elements merely happens to include the clocks.

        The clock widget can be configure()d like any other Label widget.
        Nothing special needs to be added to main(). The Clock class
            updates the widget automatically when you create the widget.

        """

        # Create window and frame
        window = tkinter.Tk()
        window.configure(background = 'white')
##        window.attributes('-fullscreen', True)
        mainframe = tkinter.Frame(width=800, height=600, background="#292b5d")
        mainframe.pack(fill="both", expand=True, padx=20, pady=20)

        clock_frame  = tkinter.Frame(window, width=800, height=600 )
        clock_frame.place(in_=mainframe, anchor="c", relx=.5, rely=.5)

        # Add the frame elements, including the clock like any other element
        #        top blank line
        #        time
        #        bottom blank line
        top_blank_label = tkinter.Label(clock_frame, text=" ").pack()
        clock1 = clock.Clock(clock_frame)
        clock1.pack()
        clock1.configure(bg='#292b5d',fg='silver',font=("alfie",200))
        bottom_blank_label = tkinter.Label(clock_frame, text=" ").pack()

 #       tkinter.Label(clock_frame, text="Have a nice day.",fg='white',bg='#292b5d').pack()

##       outside temperature
        outside_temperature = frames.Outside_Temperature(window)
        outside_temperature.place(in_=mainframe, anchor="c", relx=1/6.0, rely=.2)

##        outside_pressure
        outside_pressure = frames.Outside_Pressure(window)
        outside_pressure.place(in_=mainframe, anchor="c", relx=3/6.0, rely=.2)

##        outside humidity
        outside_humidity = frames.Outside_Humidity(window)
        outside_humidity.place(in_=mainframe, anchor="c", relx=5/6.0, rely=.2)

##        Create Room Values
##        room temperature
        room_temperature = frames.Room_Temperature(window)
        room_temperature.place(in_=mainframe, anchor="c", relx=1/6.0, rely=.8)

##        room pressure
        room_pressure = frames.Room_Pressure(window)
        room_pressure.place(in_=mainframe, anchor="c", relx=3/6.0, rely=.8)

##        room humidity
        room_humidity = frames.Room_Humidity(window)
        room_humidity.place(in_=mainframe, anchor="c", relx=5/6.0, rely=.8)

        window.mainloop()


from django.db import models
from groupings.models import Grouping
from theories.models import Theory
class Guide (models.Model):
	guide_name = models.CharField(max_length=50)
	guide_page = models.CharField(max_length=60, blank=True)
	guide_graphic_name = models.CharField(max_length=50)
	guide_created = models.DateTimeField(auto_now_add=True, blank=True)
	guide_description = models.CharField(max_length=500)
	guide_content = models.TextField()
	pay = models.BooleanField()
	guide_views = models.PositiveIntegerField(default=0, blank=True)
	guide_votes = models.PositiveIntegerField(default=0, blank=True)
	guide_meta = models.ForeignKey(Grouping, blank=True, null=True)
	guide_knowledge = models.ForeignKey(Theory, blank=True, null=True)
	def __unicode__(self):
		return self.guide_name

	class Meta:
		ordering = ['guide_created']
		permissions = (("access_pay", "can access paid guides"),)


#coding=utf-8
__author__ = 'leviathan'

class Singleton(object):
    #工作类
    class _Work(object):
        def __init__(self):
            pass
        def display(self):
            return id(self)

    #存储实例的变量
    _instance=None
    def __init__(self):
        if Singleton._instance==None:
            Singleton._instance=Singleton._Work()
    def __getattr__(self, attr):
        return getattr(self._instance,attr)

if __name__ == "__main__":
    Examle1=Singleton()
    Examle2=Singleton()
    print(id(Examle1),Examle1.display())
    print(id(Examle2),Examle2.display())

#多线程处理
#coding=utf-8
__author__ = 'leviathan'

import threading
import datetime
lock=threading.Lock()

num=0
class Work(threading.Thread):
    __instance=None
    def run(self):
        global num
        if lock.acquire():
            num=num+1
            print(num)
            print('\n')
            if Work.__instance==None:
                Work.__instance=self
        lock.release()
        return Work.__instance

def main():
    for index in range(10):
        threadexp=Work()
        threadexp.start()

if __name__=="__main__":
    main()


import numpy as np

class Kohonen(object):
    def __init__(self, vector_size=3, num_cluster=2, learning_rate=0.3, max_epoch = 5, input_vectors=[]):
        self.vector_size = vector_size
        self.num_cluster = num_cluster
        self.learning_rate = learning_rate
        self.weight = np.random.rand(vector_size, num_cluster)
        self.max_epoch = max_epoch
        self.input_vectors = input_vectors

        print("initial learning rate: " + str(self.learning_rate))
        print("initial weight: " + str(self.weight))

    def geometric_decay(self):
        self.learning_rate *= 0.2
        print("updated learning rate:" + str(self.learning_rate))

    # returns the node that is closest to the vector
    def apply_input_vector(self, vector):
        I = [None] * self.num_cluster
        closest = float("inf")
        closest_index = -1
        vector = np.array(vector)

        for col in range(0, self.num_cluster):
            print("col: " + str(col))
            print("weight col: " + str(self.weight[:,col]))
            print("vector: " + str(vector))
            I[col] = self.weight[:,col] - vector
            I[col] = np.square(I[col])
            closeness = np.sum(I[col])
            print("closeness: " + str(closeness))
            if closeness < closest:
                closest = closeness
                closest_index = col

        return vector, closest_index

    def update_weight(self, vector, col):
        print("updated weight col: " + str(col))
        self.weight[:,col] += self.learning_rate * (vector - self.weight[:,col])
        print("updated weight: " + str(self.weight))

    def train(self):
        for i in range(0, self.max_epoch):
            for input_vector in self.input_vectors:
                closest_vector, col = self.apply_input_vector(input_vector)
                self.update_weight(closest_vector, col)
            self.geometric_decay()


"""
Automatically translate python models to C
"""
from __future__ import print_function

import inspect

import numpy as np

from . import py2c
from . import special

# pylint: disable=unused-import
try:
    from types import ModuleType
    #from .modelinfo import ModelInfo  # circular import
except ImportError:
    pass
# pylint: enable=unused-import

DEPENDENCY = {
    'core_shell_kernel': ['lib/core_shell.c'],
    'fractal_sq': ['lib/fractal_sq.c'],
    'gfn4': ['lib/gfn.c'],
    'polevl': ['lib/polevl.c'],
    'p1evl': ['lib/polevl.c'],
    'sas_2J1x_x': ['lib/polevl.c', 'lib/sas_J1.c'],
    'sas_3j1x_x': ['lib/sas_3j1x_x.c'],
    'sas_erf': ['lib/polevl.c', 'lib/sas_erf.c'],
    'sas_erfc': ['lib/polevl.c', 'lib/sas_erf.c'],
    'sas_gamma': ['lib/sas_gamma.c'],
    'sas_gammaln': ['lib/sas_gammainc.c'],
    'sas_gammainc': ['lib/sas_gammainc.c'],
    'sas_gammaincc': ['lib/sas_gammainc.c'],
    'sas_J0': ['lib/polevl.c', 'lib/sas_J0.c'],
    'sas_J1': ['lib/polevl.c', 'lib/sas_J1.c'],
    'sas_JN': ['lib/polevl.c', 'lib/sas_J0.c', 'lib/sas_J1.c', 'lib/sas_JN.c'],
    'sas_Si': ['lib/Si.c'],
}

DEFINES = frozenset("M_PI M_PI_2 M_PI_4 M_SQRT1_2 M_E NAN INFINITY M_PI_180 M_4PI_3".split())

def convert(info, module):
    # type: ("ModelInfo", ModuleType) -> bool
    """
    Convert Iq, Iqxy, form_volume, etc. to c

    Returns list of warnings
    """
    # Check if there is already C code
    if info.source or info.c_code is not None:
        return

    public_methods = ("Iq", "Iqac", "Iqabc", "Iqxy",
            "form_volume", "shell_volume", "effective_radius")

    tagged = [] # type: List[str]
    translate = [] # type: List[Callable]
    for function_name in public_methods:
        function = getattr(info, function_name)
        if callable(function):
            if getattr(function, 'vectorized', None):
                return  # Don't try to translate vectorized code
            tagged.append(function_name)
            translate.append((function_name, function))
    if not translate:
        # nothing to translate---maybe Iq, etc. are already C snippets?
        return

    libs = []  # type: List[str]
    snippets = []  # type: List[str]
    constants = {} # type: Dict[str, Any]
    code = {}  # type: Dict[str, str]
    depends = {}  # type: Dict[str, List[str]]
    while translate:
        function_name, function = translate.pop(0)
        filename = function.__code__.co_filename
        escaped_filename = filename.replace('\\', '\\\\')
        offset = function.__code__.co_firstlineno
        refs = function.__code__.co_names
        depends[function_name] = set(refs)
        source = inspect.getsource(function)
        for name in refs:
            if name in tagged or name in DEFINES:
                continue
            tagged.append(name)
            obj = getattr(module, name, None)
            if obj is None:
                pass # ignore unbound variables for now
                #raise ValueError("global %s is not defined" % name)
            elif callable(obj):
                if getattr(special, name, None):
                    # special symbol: look up depenencies
                    libs.extend(DEPENDENCY.get(name, []))
                else:
                    # not special: add function to translate stack
                    translate.append((name, obj))
            elif isinstance(obj, (int, float, list, tuple, np.ndarray)):
                constants[name] = obj
                # Claim all constants are declared on line 1
                snippets.append('#line 1 "%s"\n'%escaped_filename)
                snippets.append(py2c.define_constant(name, obj))
            elif isinstance(obj, special.Gauss):
                for var, value in zip(("N", "Z", "W"), (obj.n, obj.z, obj.w)):
                    var = "GAUSS_"+var
                    constants[var] = value
                    snippets.append('#line 1 "%s"\n'%escaped_filename)
                    snippets.append(py2c.define_constant(var, value))
                #libs.append('lib/gauss%d.c'%obj.n)
                source = (source.replace(name+'.n', 'GAUSS_N')
                          .replace(name+'.z', 'GAUSS_Z')
                          .replace(name+'.w', 'GAUSS_W'))
            else:
                raise TypeError("Could not convert global %s of type %s"
                                % (name, str(type(obj))))

        # add (possibly modified) source to set of functions to compile
        code[function_name] = (source, filename, offset)

    # remove duplicates from the dependecy list
    unique_libs = []
    for filename in libs:
        if filename not in unique_libs:
            unique_libs.append(filename)

    # translate source
    ordered_code = [code[name] for name in py2c.ordered_dag(depends) if name in code]
    functions, warnings = py2c.translate(ordered_code, constants)
    snippets.extend(functions)

    # update model info
    info.source = unique_libs
    info.c_code = "".join(snippets)
    info.Iq = info.Iqac = info.Iqabc = info.Iqxy = info.form_volume = None

    return warnings


#El nombre de este archivo debia ser 24to10 pero en al importarlo en los test daba un problemita asi que por eso agregue la E de Examen
def Conversor(tiempo):
    #ponemos el string a una variable local con la que trabajaremos quitando posibles espacios en blanco
    textito = tiempo.strip()
    hora=''
    minutos=''
    dia='' #Esto es donde se asigna el 'a.m.' o 'p.m.'
    hora += textito[0]
    hora += textito[1]
    hora = int(hora)
    minutos += textito[3]
    minutos += textito[4]

    if hora > 12:
        hora = hora-12
        dia = 'p.m.'

    elif(hora==12):
        dia = 'p.m.'

    elif(hora==00):
        hora = 12
        dia = 'a.m.'

    else:
        dia = 'a.m.'

    hora = str(hora)
    #.zfill ayuda a rellenar con ceros a la izquierda segun lo pidas, sirve mas que nada para la hora, (solo sirve en strings)
    hora = hora.zfill(2)
    minutos = minutos.zfill(2)

    return hora+':'+minutos+dia


import pandas as pd
import re
x = pd.read_csv('bunruidb.txt', encoding='shift-jis')
kensaku_go = input("検索する語を入力してください：")
kensaku_go = re.split("[　, ]", kensaku_go)

def kensaku(kensaku_go):
    bangou = 0
    print('\n')
    for i in range(len(x)):
        if (x.iloc[i][12] == kensaku_go[0]):
            bangou = bangou + 1
            print("{}番目：{}の検索結果を表示します。\n".format(bangou, kensaku_go[0]))
            print(x.iloc[i], '\n')
        elif (x.iloc[i][13] == kensaku_go[0]):
            bangou = bangou + 1
            print("{}番目：{}の検索結果を表示します。\n".format(bangou, kensaku_go[0]))
            print(x.iloc[i], '\n')

    if (bangou == 0):
        print("！{}は語彙目録には存在しない語です。".format(kensaku_go[0]))

def fukusuu_kensaku(kensaku_hairetu):
    bangou = 0
    print('\n')
    for i in kensaku_hairetu:
        for j in range(len(x)):
            if (x.iloc[j][12] == i):
                bangou = bangou + 1
                print("{}番目：{}の検索結果を表示します。\n".format(bangou, i))
                print(x.iloc[j], '\n')
            elif (x.iloc[j][13] == i):
                bangou = bangou + 1
                print("{}番目：{}の検索結果を表示します。\n".format(bangou, i))
                print(x.iloc[j], '\n')
        bangou = 0

    if (bangou == 0):
        print("！{}は語彙目録には存在しない語です。".format(i))

if (len(kensaku_go) <= 1):
    kensaku(kensaku_go)
else:
    fukusuu_kensaku(kensaku_go)


import random

n1 = input('primeiro aluno: ')
n2 = input('Segundo aluno: ')
n3 = input('Digite o 3 aluno')
n4 = input('Digite o 4 aluno: ')

lista = [n1, n2, n3 ,n4]

esc = random.choice(lista)
print('O aluno escolhido foi {}'.format(esc))


#!/usr/bin/env python3.4
import math
import random
import sys

def graticles(xmin, xmax, xsteps, ymin, ymax, ysteps):
    for x in range(xmin, xmax, int((xmax - xmin) / xsteps)):
        for y in range(ymin, ymax, int((ymax - ymin) / ysteps)):
            yield (x, y)

def box_verticies(llxy, xsize, ysize):
    return [
        llxy,
        (llxy[0] + xsize, llxy[1]),
        (llxy[0] + xsize, llxy[1] + ysize),
        (llxy[0], llxy[1] + ysize),
    ]

def box_center(llxy, xsize, ysize):
    return llxy[0] + (xsize / 2), llxy[1] + (ysize / 2)

def box_triangles(llxy, xsize, ysize):
    center = box_center(llxy, xsize, ysize)
    p1 = (llxy[0] + xsize, llxy[1])
    p2 = (llxy[0] + xsize, llxy[1] + ysize)
    p3 = (llxy[0], llxy[1] + ysize)
    return [
        [llxy, p1, center],
        [p1, p2, center],
        [p2, p3, center],
        [p3, llxy, center]
    ]

def gen_random_points(listsize):
    return [(random.uniform(-180, 180), random.uniform(-90, 90)) for x in range(listsize)]

def gen_odd_polygon_points(center, numpoints):
    return [() for x in range(numpoints)]

def wkt_point(xy):
    return "POINT ( {0[0]} {0[1]} )".format(xy)

def wkt_linestring(points):
    wkt = "LINESTRING ( "
    wkt += ", ".join(["{0[0]} {0[1]}".format(p) for p in points])
    wkt += " )"
    return wkt

def wkt_linestring_box(points):
    wkt = "LINESTRING ( "
    wkt += ", ".join(["{0[0]} {0[1]}".format(p) for p in points])
    wkt += ", {0[0]} {0[1]}".format(points[0])
    wkt += " )"
    return wkt

def wkt_polygon(points):
    wkt = "POLYGON (( "
    wkt += ", ".join(["{0[0]} {0[1]}".format(p) for p in points])
    wkt += ", {0[0]} {0[1]}".format(points[0])
    wkt += " ))"
    return wkt

def bound(bounds, n):
    if n < bounds[0]:
        n = bounds[0]
    elif n > bounds[1]:
        n = bounds[1]
    return n

def ran_cirle(ab, numpoints, minradius, maxradius, xbounds, ybounds):
    points = []
    for t in frange(0, 2 * math.pi, math.pi/ numpoints):
        radius = random.uniform(minradius, maxradius)
        x = ab[0] + radius * math.cos(t)
        y = ab[1] + radius * math.sin(t)
        points.append((bound(xbounds, x), bound(ybounds, y)))
    return points

def frange(start, stop, step):
    while start < stop:
        yield start
        start += step

def main(argv):
    xmax = 180
    xmin = -xmax
    ymax = 90
    ymin = -ymax
    xsteps = 18
    ysteps = 9
    xsize = int((xmax - xmin) / xsteps)
    ysize = int((ymax - ymin) / ysteps)

    for graticle in graticles(xmin, xmax, xsteps, ymin, ymax, ysteps):
        print(wkt_polygon(ran_cirle(graticle, 10,
                                    random.randint(1, xsize*2),
                                    random.randint(1, ysize*2),
                                    (xmin, xmax),
                                    (ymin, ymax))))
        # boxvs = box_verticies(graticle, xsize, ysize)
        # print(wkt_point(graticle))  # lowerLeft
        # print(wkt_point(box_center(graticle, xsize, ysize)))  # box center
        # print(wkt_linestring_box(boxvs))  # box linestring
        # print(wkt_polygon(boxvs))  # box polygon
        # for btri in box_triangles(graticle, xsize, ysize):
        #    print(wkt_polygon(btri))
        # print(wkt_polygon(
        #     sorted(gen_random_points(random.randint(3, 100)), key=lambda element: (element[0], element[1]))))

if __name__ == "__main__":
    main(sys.argv)


data = []
count = 0
with open('reviews.txt', 'r') as f:
	for line in f:
		data.append(line)
		count += 1 # count = count +1
		if count % 1000 == 0:
			print(len(data))
print('Finished loading', len(data), 'in total')

sum_len = 0
for d in data:
	sum_len = sum_len + len(d)
print('留言平均長度為', sum_len/len(data))

new = []
for  d in data:
	if len(d) < 100:
		new.append(d)
print('Total', len(new), 'less than 100')
print(new[2])

good = []
for d in data:
	if  'good' in d:
		good.append(d)
print('共有', len(good), '筆留言提到good')
print(good[0])


# uncompyle6 version 3.7.4
# Python bytecode 2.4 (62061)
# Decompiled from: Python 3.6.9 (default, Apr 18 2020, 01:56:04)
# [GCC 8.4.0]
# Embedded file name: build/bdist.linux-i686/egg/ks/schema/html/html.py
# Compiled at: 2008-12-22 08:23:24
"""HTML field class for the Zope 3 based ks.schema.html package

$Id: html.py 23861 2007-11-25 00:13:00Z xen $
"""
__author__ = 'Anatoly Zaretsky'
__license__ = 'ZPL'
__version__ = '$Revision: 23861 $'
__date__ = '$Date: 2007-11-25 02:13:00 +0200 (Sun, 25 Nov 2007) $'
from zope.schema import Text
from zope.interface import implements
from interfaces import IHTML

class HTML(Text):
    __module__ = __name__
    implements(IHTML)


import string
import nltk
from spacy.lang.en import English
from nltk.corpus import stopwords
from nltk.stem import SnowballStemmer
import re

nltk.download('stopwords')

def tokenize_preprocess_corpus(reports):
    '''This function uses spacy tokenizer'''

    nlp = English()
    # Create a Tokenizer with the default settings for English
    # including punctuation rules and exceptions
    tokenizer = nlp.Defaults.create_tokenizer(nlp)

    # this will be a nested list of tokens [['word1', 'word2',...], [...], [...], ...]
    tokenized_reports = []

    for report in reports:
        report = cleanText(report)
        tokens = tokenizer(report.strip())
        # 'token' is not a string type but rather spacy.token type
        # converting the spacy tokens into lists of words
        new_tokens = [str(token) for token in tokens]

        # removes the stopwords from the original tokens (still working-not completed)
        new_tokens = remove_stopwords(new_tokens)

        new_tokens=stemming(new_tokens)

        # appending unique word tokens only per document
        tokenized_reports.append(new_tokens)

    return tokenized_reports

def cleanText(text):
        text = re.sub('\|\|\|', ' ', text)
        text = text.lower()
        text = text.replace('x', '')
        return text

def stemming(tokens):

    stemmer = SnowballStemmer('english')
    stemmed_words=[]
    for token in tokens:
        stemmed_words.append(stemmer.stem(token))
    return stemmed_words

def remove_stopwords(tokens):
    '''This function will remove the stopwords'''

    # use this if you use stopword list directly from nltk

    stopWords = set(stopwords.words('english'))

    word_tokens = []

    for token in tokens:
        # removing stopwords and punctuations
        if token.lower() not in stopWords:
            word_tokens.append(token)
            # print(token)

    return word_tokens

#this was a test basis manual tokenization - not using for this classification
def manual_tokenization(reports):
    '''This function will tokenize the document and returns a list of tokenized doc'''

    # this will be a nested list of tokens [['word1', 'word2',...], [...], [...], ...]
    tokenized_reports = []

    for report in reports:
        # lowercase the string
        report = report.lower()

        # removing punctuation symbols
        report = report.translate(str.maketrans('', '', string.punctuation))

        # tokenize the text based on white space
        tokens = report.split()

        tokenized_reports.append(tokens)

    print(tokenized_reports[0])

    return tokenized_reports