class MatchEngine(OnePyEnvBase):

    def __init__(self, trade_log):
        self.long_log_pure = defaultdict(deque)
        self.long_log_with_trigger = defaultdict(deque)
        self.short_log_pure = defaultdict(deque)
        self.short_log_with_trigger = defaultdict(deque)
        self.finished_log = []
        self.trade_log: TradeLogBase = trade_log
        self.left_trade_settled = False

    def _append_finished(self, buy_order, sell_order, size):
        log = self.trade_log(buy_order, sell_order, size).generate()
        self.finished_log.append(log)

    def _search_father(self, order, log_with_trigger):
        for i in log_with_trigger:
            if i.mkt_id == order.father_mkt_id:
                self._append_finished(i, order, order.size)
                log_with_trigger.remove(i)

                break

    def _del_in_mkt_dict(self, mkt_id):
        if mkt_id in self.env.orders_child_of_mkt_dict:
            del self.env.orders_child_of_mkt_dict[mkt_id]

    def _change_order_size_in_pending_mkt_dict(self, mkt_id, track_size):
        pending_mkt_dict = self.env.orders_child_of_mkt_dict

        if mkt_id in pending_mkt_dict:
            for order in pending_mkt_dict[mkt_id]:
                order.size = track_size

    def _pair_one_by_one(self, order_list, sell_size, order, counteract=False):
        buy_order = order_list.popleft()
        buy_size = buy_order.track_size
        diff = buy_order.track_size = buy_size - sell_size

        if diff > 0:
            self._append_finished(buy_order, order, sell_size)
            order_list.appendleft(buy_order)

            if counteract:  # 修改dict中订单size
                self._change_order_size_in_pending_mkt_dict(
                    buy_order.mkt_id, buy_order.track_size)

        elif diff == 0:
            self._append_finished(buy_order, order, sell_size)

            if counteract:
                self._del_in_mkt_dict(buy_order.mkt_id)

        else:
            self._append_finished(buy_order, order, buy_size)
            sell_size -= buy_size

            if counteract:
                self._del_in_mkt_dict(buy_order.mkt_id)
            self._pair_one_by_one(order_list, sell_size, order, counteract)

    def _pair_order(self, long_or_short, order):  # order should be sell or short cover
        if long_or_short == 'long':
            log_pure = self.long_log_pure[order.ticker]
            log_with_trigger = self.long_log_with_trigger[order.ticker]
        elif long_or_short == 'short':
            log_pure = self.short_log_pure[order.ticker]
            log_with_trigger = self.short_log_with_trigger[order.ticker]

        sell_size = order.size

        if isinstance(order.signal, SignalByTrigger):
            self._search_father(order, log_with_trigger)

        else:

            try:
                self._pair_one_by_one(log_pure, sell_size, order)
            except IndexError:
                self._pair_one_by_one(log_with_trigger, sell_size, order, True)

    def match_order(self, order):
        if order.action_type == ActionType.Buy:
            order.track_size = order.size

            if order.is_pure():
                self.long_log_pure[order.ticker].append(order)
            else:
                self.long_log_with_trigger[order.ticker].append(order)
        elif order.action_type == ActionType.Short:
            order.track_size = order.size

            if order.is_pure():
                self.short_log_pure[order.ticker].append(order)
            else:
                self.short_log_with_trigger[order.ticker].append(order)

        elif order.action_type == ActionType.Sell:
            self._pair_order('long', order)

        elif order.action_type == ActionType.Cover:
            self._pair_order('short', order)

    def append_left_trade_to_log(self):

        def settle_left_trade(unfinished_order):
            log = self.trade_log(unfinished_order, None,
                                 unfinished_order.track_size).settle_left_trade()
            self.finished_log.append(log)

        for ticker in self.env.tickers:

            for order in self.long_log_pure[ticker]:
                settle_left_trade(order)

            for order in self.long_log_with_trigger[ticker]:
                settle_left_trade(order)

            for order in self.short_log_pure[ticker]:
                settle_left_trade(order)

            for order in self.short_log_with_trigger[ticker]:
                settle_left_trade(order)

    @memo('trade_log')
    def generate_trade_log(self):
        if self.left_trade_settled is False:
            self.append_left_trade_to_log()
            self.left_trade_settled = True

        log_dict = defaultdict(list)

        for log in self.finished_log:
            log_dict['ticker'].append(log.ticker)
            log_dict['entry_date'].append(log.entry_date)
            log_dict['entry_price'].append(log.entry_price)
            log_dict['entry_type'].append(log.entry_type)
            log_dict['size'].append(log.size)
            log_dict['exit_date'].append(log.exit_date)
            log_dict['exit_price'].append(log.exit_price)
            log_dict['exit_type'].append(log.exit_type)
            log_dict['pl_points'].append(log.pl_points)
            log_dict['re_pnl'].append(log.re_pnl)
            log_dict['comm'].append(log.commission)

        return pd.DataFrame(log_dict) class OnePyEnvBase:
    """作为基类，提供env共享单例给各个模块"""
    env = Environment class TradeLogBase(OnePyEnvBase, abc.ABC):

    buy: float = None
    sell: float = None
    size: float = None

    entry_date: str = field(init=False)
    exit_date: str = field(init=False)

    entry_price: float = field(init=False)
    exit_price: float = field(init=False)

    entry_type: str = field(init=False)
    exit_type: str = field(init=False)

    pl_points: float = field(init=False)
    re_pnl: float = field(init=False)

    commission: float = field(init=False)

    @abc.abstractmethod
    def generate(self):
        raise NotImplementedError

    def _earn_short(self):
        return -1 if self.buy.action_type == ActionType.Short else 1

    @staticmethod
    def _get_order_type(order):
        if isinstance(order.signal, SignalByTrigger):
            return order.signal.order_type.value
        else:
            return order.order_type.value

    @abc.abstractmethod
    def settle_left_trade(self):
        raise NotImplementedError

    @property
    def ticker(self):
        return self.buy.ticker class AvgPriceSeries(SeriesBase):
    name = 'avg_price'

    def update_order(self, ticker, size, execute_price, last_position,
                     last_avg_price, new_position, long_or_short='long'):

        if new_position == 0:
            new_value = 0

        elif new_position > last_position:
            new_value = (last_position * last_avg_price +
                         size*execute_price)/new_position
        else:
            new_value = last_avg_price

        self._append_value(ticker, new_value, long_or_short) class CommissionSeriesBase(SeriesBase):
    name = 'commission'

    @abc.abstractmethod
    def update_order(self, ticker, size, execute_price, action_type,
                     last_commission,  long_or_short='long'):
        raise NotImplementedError class HoldingPnlSeriesBase(SeriesBase):
    name = 'holding_pnl'

    @abc.abstractmethod
    def update_order(self, ticker, cur_price, new_avg_price,
                     new_position, long_or_short='long'):
        raise NotImplementedError

    @abc.abstractmethod
    def update_barly(self, order_executed: bool):
        raise NotImplementedError class MarginSeriesBase(SeriesBase):
    name = 'margin'

    @abc.abstractmethod
    def update_order(self, ticker, long_or_short='long'):
        raise NotImplementedError

    @abc.abstractmethod
    def update_barly(self):
        raise NotImplementedError class MarketValueSeriesBase(SeriesBase):
    name = 'market_value'

    @abc.abstractmethod
    def update_order(self, ticker, cur_price, new_position,
                     long_or_short='long'):

        raise NotImplementedError

    @abc.abstractmethod
    def update_barly(self, order_executed: bool):
        raise NotImplementedError class MoneySeries(OnePyEnvBase, UserList):

    def __init__(self, name: str, initial_value: int,
                 maxlen: int = None) -> None:
        super().__init__()
        self.name = name

        if maxlen:
            self.data = deque(
                [dict(date=self.env.fromdate, value=initial_value)],
                maxlen=maxlen)  # type: Deque[Dict[str,Union[str,float]]]
        else:
            # type: List[Dict[str,Union[str,float]]]
            self.data = [dict(date=self.env.fromdate, value=initial_value)]

    def change_initial_value(self, value: int):
        self.data[0]['value'] = value

    def latest(self, index: int = -1) -> float:
        return self.data[index]['value']

    def dataframe(self) -> pd.DataFrame:
        dataframe = pd.DataFrame(self.data)
        dataframe.rename(columns=dict(value=self.name), inplace=True)
        dataframe.set_index('date', inplace=True)
        dataframe.index = pd.to_datetime(dataframe.index)
        result = dataframe[~dataframe.index.duplicated(keep='last')]

        first = dataframe.ix[: 1]
        result = pd.concat([first, result])
        result.sort_index(inplace=True)

        return result

    def plot(self):
        self.dataframe().plot() class PositionSeries(SeriesBase):
    name = 'position'

    @staticmethod
    def direction(action_type):
        if action_type in [ActionType.Buy, ActionType.Short]:
            return 1

        elif action_type in [ActionType.Sell, ActionType.Cover]:
            return -1

    def update_order(self, ticker, size, action_type,
                     last_position, long_or_short='long'):

        new_value = last_position + size * self.direction(action_type)
        self._append_value(ticker, new_value, long_or_short) class RealizedPnlSeriesBase(SeriesBase):
    name = 'realized_pnl'

    @abc.abstractmethod
    def update_order(self, ticker, size, execute_price, action_type,
                     last_avg_price, long_or_short='long'):
        raise NotImplementedError
import abc
from OnePy.sys_module.components.match_engine import MatchEngine
from OnePy.sys_module.metabase_env import OnePyEnvBase
from OnePy.sys_module.models.base_log import TradeLogBase
from OnePy.sys_module.models.base_series import (AvgPriceSeries,
                                                 CommissionSeriesBase,
                                                 HoldingPnlSeriesBase,
                                                 MarginSeriesBase,
                                                 MarketValueSeriesBase,
                                                 MoneySeries, PositionSeries,
                                                 RealizedPnlSeriesBase)



class RecorderBase(OnePyEnvBase, abc.ABC):

    def __init__(self):
        self.env.recorders.update({self.__class__.__name__: self})
        self.env.recorder = self

        self.initial_cash: int = 100000
        self.per_comm: int = 1
        self.per_comm_pct: float = None
        self.margin_rate: float = 0.1


        self.holding_pnl: HoldingPnlSeriesBase = None