# -*- encoding:utf-8 -*-
"""
    交易订单模块
"""

import math

import numpy as np

from ultron.ump.market.futures import FuturesCn
from ultron.ump.market.market import MarketMixin
from ultron.ump.core import env


class OrderMarket(MarketMixin):
    """根据Order对象，设置混入类MarketMixin中symbol_name"""

    def __init__(self, order):
        """
        设置混入类MarketMixin中symbol_name，以获取symbol_market等信息
        :param order: AbuOrder对象
        """
        if isinstance(order, Order):
            self.symbol_name = order.buy_symbol
        else:
            raise TypeError('order is AbuOrder object!!!')


# noinspection PyAttributeOutsideInit
class Order(object):
    """交易订单类"""

    # 多个因子买入条件可能生成几百万个order对象使用__slots__降低内存消耗
    __slots__ = ('order_deal', 'buy_symbol', 'buy_date', 'buy_factor',
                 'buy_factor_class', 'buy_price', 'buy_cnt', 'buy_pos',
                 'sell_date', 'buy_type_str', 'expect_direction', 'sell_type',
                 'keep_days', 'sell_price', 'sell_type_extra', 'ml_features')

    def __init__(self):
        """初始设置只需要将order_deal设置未成交状态"""
        self.order_deal = False

    def fit_buy_order(self, day_ind, factor_object):
        """
        根据买入交易日当当天数据以及买入因子，拟合计算买入订单
        :param day_ind: 买入交易发生的时间索引，即对应self.kl_pd.key
        :param factor_object: ABuFactorBuyBases子类实例对象
        """
        kl_pd = factor_object.kl_pd
        # 要执行买入当天的数据
        kl_pd_buy = kl_pd.iloc[day_ind + 1]
        # 买入因子名称
        factor_name = factor_object.factor_name if hasattr(
            factor_object, 'factor_name') else 'unknown'
        # 日内滑点决策类
        slippage_class = factor_object.slippage_class
        # 仓位管理类设置
        position_class = factor_object.position_class
        # 初始资金，也可修改策略使用剩余资金
        read_cash = factor_object.capital.read_cash
        # 实例化滑点类
        fact = slippage_class(kl_pd_buy, factor_name)
        # 执行fit_price, 计算决策买入价格
        bp = fact.fit()
        # 如果滑点类中决定不买入，撤单子，bp就返回正无穷
        if bp < np.inf:
            """
                实例化仓位管理类
                仓位管理默认保证金比例是1，即没有杠杆，修改ABuPositionBase.g_deposit_rate可提高融资能力，
                如果margin＝2－>ABuPositionBase.g_deposit_rate = 0.5, 即只需要一半的保证金，也可同过构建
                时使用关键字参数完成保证金比例传递
            """
            position = position_class(kl_pd_buy, factor_name,
                                      factor_object.kl_pd.name, bp, read_cash,
                                      **factor_object.position_kwargs)

            market = position.symbol_market  #env.g_market_target if env.g_use_env_market_set else position.symbol_market
            """
                由于模块牵扯复杂，暂时不迁移保证金融资相关模块，期货不使用杠杆，即回测不牵扯资金总量的评估
                if market == EMarketTargetType.E_MARKET_TARGET_FUTURES_CN:
                    deposit_rate = 0.10
                    q_df = AbuFuturesCn().query_symbol(factor_object.kl_pd.name)
                    if q_df is not None:
                        deposit_rate = q_df.min_deposit.values[0]
                    # 重新设置保证金比例
                    position.deposit_rate = deposit_rate
            """
            # 执行fit_position，通过仓位管理计算买入的数量
            bc = position.fit_position(factor_object)
            if np.isnan(bc):
                return

            if market != env.EMarketTargetType.E_MARKET_TARGET_TC:
                # 除了比特币市场外，都向下取整数到最小交易单位个数
                buy_cnt = int(math.floor(bc))
            else:
                # 币类市场可以买非整数个, 保留三位小数
                buy_cnt = round(bc, 3)

            if market == env.EMarketTargetType.E_MARKET_TARGET_US:
                # 美股1
                min_cnt = 1
            elif market == env.EMarketTargetType.E_MARKET_TARGET_TC:
                # 国内一般只支持到0.01个
                min_cnt = 0.01
            elif market == env.EMarketTargetType.E_MARKET_TARGET_CN:
                # A股最小100一手
                min_cnt = 100
                # 向最小的手量看齐
                buy_cnt -= buy_cnt % min_cnt
            elif market == env.EMarketTargetType.E_MARKET_TARGET_FUTURES_CN:
                # 国内期货，查询最少一手单位
                min_cnt = FuturesCn().query_min_unit(factor_object.kl_pd.name)
                # 向最小的手量看齐
                buy_cnt -= buy_cnt % min_cnt
            else:
                raise TypeError(
                    'ABuEnv.g_market_target ERROR, market={}, g_market_target={}'
                    .format(market, env.g_market_target))

            if buy_cnt < min_cnt:
                # 不够买最少单位量
                return

            # 如下生成order内部数据
            self.buy_symbol = kl_pd.name
            # 订单写入买入日期
            self.buy_date = int(kl_pd_buy.date)
            # 订单写入买入因子名字
            self.buy_factor = factor_name
            # 订单对象中添加买入因子类名，和buy_factor不同没有具体参数等唯一key
            self.buy_factor_class = factor_object.__class__.__name__
            # 订单写入买入价格
            self.buy_price = bp
            # 订单写入买入数量
            self.buy_cnt = buy_cnt
            # 订单写入仓位管理类名称
            self.buy_pos = position.__class__.__name__
            # 订单写入买入类型，call or put
            self.buy_type_str = factor_object.buy_type_str
            # 订单写入买入因子期望方向
            self.expect_direction = factor_object.expect_direction

            # 如下卖出信息具体写入在fit_sell_order中
            # 订单卖出时间
            self.sell_date = None
            # 订单卖出类型，keep：持有
            self.sell_type = 'keep'
            # 交易日持有天数
            self.keep_days = 0

            # 订单卖出价格
            self.sell_price = None
            # 订单卖出额外信息
            self.sell_type_extra = ''

            # 订单买入，卖出特征
            self.ml_features = None
            # 订单形成
            self.order_deal = True

    def fit_sell_order(self, day_ind, factor_object):
        """
        根据卖出交易日当当天数据以及卖出因子，拟合计算卖出信息，完成订单
        :param day_ind: 卖出交易发生的时间索引，即对应self.kl_pd.key
        :param factor_object: FactorSellBase子类实例对象
        """

        if self.sell_type != 'keep':
            # 保证外部不需要过滤单子，内部自己过滤已经卖出成交的订单
            return

        # 卖出策略中进行特征合成, 以及ump拦截卖出行为
        if factor_object.make_sell_order(self, day_ind):
            kl_pd_sell = factor_object.kl_pd.iloc[day_ind + 1]

            # 日内滑点决策类
            slippage_class = factor_object.slippage_class
            # 卖出因子名称
            factor_name = factor_object.factor_name if hasattr(
                factor_object, 'factor_name') else 'unknown'
            # 实例化日内滑点决策类，进行具体卖出价格决策
            sell_price = slippage_class(kl_pd_sell, factor_name).fit()

            if sell_price == -np.inf:
                # 如果卖出执行返回负无穷说明无法卖出，例如跌停
                return

            self.sell_price = sell_price
            # 卖出原因其它描述
            sell_type_extra = factor_object.sell_type_extra if hasattr(
                factor_object, 'sell_type_extra') else 'unknown'
            self.sell_type_extra = sell_type_extra
            if self.buy_type_str == 'call':
                # call卖出类型:  win = self.sell_price > self.buy_price
                self.sell_type = 'win' if self.sell_price > self.buy_price else 'loss'
            else:
                # put卖出类型:  loss = self.sell_price > self.buy_price
                self.sell_type = 'loss' if self.sell_price > self.buy_price else 'win'
            # 卖出日期写入单子
            self.sell_date = int(kl_pd_sell.date)

    def __str__(self):
        """打印对象显示：buy_symbol， buy_price， buy_cnt， buy_date，buy_factor，sell_date，sell_type， sell_price"""
        return 'buy Symbol = ' + str(self.buy_symbol) + '\n' \
               + 'buy Prices = ' + str(self.buy_price) + '\n' \
               + 'buy cnt = ' + str(self.buy_cnt) + '\n' \
               + 'buy date = ' + str(self.buy_date) + '\n' \
               + 'buy factor = ' + str(self.buy_factor) + '\n' \
               + 'sell date = ' + str(self.sell_date) + '\n' \
               + 'sell type = ' + str(self.sell_type) + '\n' \
               + 'sell Price = ' + str(self.sell_price) + '\n'

    __repr__ = __str__