from abc import ABCMeta, abstractmethod


class BaseStrategy(metaclass=ABCMeta):
    """
    Base class for strategies. Ties accounts to a strategy function. Provides a basic interface such that results
    are analyzable for backtest engine.

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    Must store results in self.signals, self.positions, self.merged_positions, self.total_balances
    and self.merged_total_balances after self.run() in specified format in order to be analyzable by backtest
    engine.
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    """

    @abstractmethod
    def run(self, data, reference_currency):
        """
        Runs strategy_func with supplied data. Data handling must include handling of lookback time frame.
        Reference currency is used to calculate total balances of accounts after executing strategy_func.

        :param data:  dict with:
                     keys: currency pair as tuple of strings: (currency_0, currency_1)
                     values: list of floats as exchange rates per time step t or array of floats
                     {(base, quote): [rate_0, rate_1, ..., rate_t],
                      (base, quote): [rate_0, rate_1, ..., rate_t],
                      ...}

                     All values (list or array of floats) of all keys (currency pairs) have to be of same length.

                     If historic data shall be used by strategy_func (cf. lookback), historic data has to be prepended
                     manually to param:data, e.g.:
                     {(base, quote): [rate_-3, rate_-2, rate_-1, rate_0, rate_1, ... rate_t],
                      (base, quote): [rate_-3, rate_-2, rate_-1, rate_0, rate_1, ... rate_t],
                      ...}

                      Data will be looped through one by one (time frame specified by lookback and current time step t
                      [size: lookback + 1]). Start time step is at index=lookback.

                      Exchange rate is expressed in FX notation convention (direct notation, not volume notation)
                      1.2 EUR|USD --> 1.2 USD for 1 EUR
        :param reference_currency: str,
                                   reference currency in which to bill total balances of all accounts
        :return: reference_currency: str,
                                     see param:reference_currency
                 self.signals: list of lists,
                               seeDocstring at self.signals
                 self.positions: dict,
                                 see Docstring at self.positions
                 self.merged_positions: dict,
                                        see Docstring at self.merged_positions
                 self.total_balances: list of lists,
                                      see Docstring at self.total_balances
                 self.merged_total_balances: list,
                                             see Docstring at self.merged_total_balances
        """

    @abstractmethod
    def reset(self):
        """
        Resets internal storage of FXStrategy class and storage of accounts as well as account clocks (if account class
        implements time discrete state machine logic). Total reset.
        :return: None
        """

    @property
    @abstractmethod
    def name(self):
        """
        Implement self.name={str} as class attribute
        """

    @property
    @abstractmethod
    def lookback(self):
        """
        Implement self.lookback={int} as class attribute. Lookback specifies number of time steps of historic data,
        which shall be fed to strategy_func additionally.
        """

    @property
    @abstractmethod
    def signals(self):
        """
        self.signals: list of lists,
                  gives all signals per account and per time step, which are generated by strategy decision function

                     | signals of account 0               | signals of account 1               |...
                     V                                    V                                    V
                  [[[acc_0_sig_t_0, acc_0_sig_t_0, ...], [acc_1_sig_t_0, acc_1_sig_t_0, ...], [...]],   <-- time step 0
                   [[acc_0_sig_t_1, acc_0_sig_t_1, ...], [acc_1_sig_t_1, acc_1_sig_t_1, ...], [...]],   <-- time step 1
                   ...]

                  access data via: time_step, acc, signal_nr
        """

    @property
    @abstractmethod
    def positions(self):
        """
        self.positions: dict: currency (str) as key, list of lists as values of position of account n at time
                          step t gives all positions (currencies) and their value (size, amount, volume) per account and
                          per time step

                                       | account 0  | account 1  ...  | account n
                                       V            V                 V
                          {currency: [[acc_0_val_0, acc_1_val_0, ..., acc_n_val_0],    <-- time step 0
                                      [acc_0_val_1, acc_1_val_1, ..., acc_n_val_1],    <-- time step 1
                                       ... ... ...
                                      [acc_0_val_t, acc_1_val_t, ..., acc_n_val_t]],   <-- time step n
                           currency: ...}

                           access data via: position, time_step, acc_nr
        """

    @property
    @abstractmethod
    def merged_positions(self):
        """
        self.merged_positions: dict: currency (str) as key, list as values per time step
                                    sums up all similar positions across all accounts to one
                                    composite position (currency) per time step

                                    {currency: [val_0, ..., val_t],
                                     currency: [val_0, ..., val_t]}

                                     access data via: position, time_step
        """

    @property
    @abstractmethod
    def total_balances(self):
        """
        self.total_balances: list of lists,
                               gives total account value (over all positions) in specified reference currency per each
                               account and time step

                                 | account 0        | account 1        | ...
                                 V                  V                  V
                               [[balance_acc_0_t_0, balance_acc_1_t_0, ...],    <-- time step 0
                                [balance_acc_0_t_1, balance_acc_1_t_1, ...],    <-- time step 1
                                ...]

                                access data via: time_step, acc_nr
        """

    @property
    @abstractmethod
    def merged_total_balances(self):
        """
        self.merged_total_balances: list,
                                     sums up total balances of all accounts per each time step; total balances are
                                     all set to same reference currency; resembles total value of strategy with
                                     respect to all used accounts per time step

                                      |time step 0 |time step 1 |...
                                      V            V            V
                                     [balance_t_0, balance_t_1, ....]

                                     access data via: time_step
        """

    @property
    @abstractmethod
    def accounts(self):
        """
        Implement self.accounts={list} as list of account objects (inherited from BaseAccount base class)
        """
