"""
Requires Catalyst version 0.3.0 or above
Tested on Catalyst version 0.3.3

This example aims to provide an easy way for users to learn how to
collect data from any given exchange and select a subset of the available
currency pairs for trading. You simply need to specify the exchange and
the market (base_currency) that you want to focus on. You will then see
how to create a universe of assets, and filter it based the market you
desire.

The example prints out the closing price of all the pairs for a given
market in a given exchange every 30 minutes. The example also contains
the OHLCV data with minute-resolution for the past seven days which
could be used to create indicators. Use this code as the backbone to
create your own trading strategy.

The lookback_date variable is used to ensure data for a coin existed on
the lookback period specified.

To run, execute the following two commands in a terminal (inside catalyst
environment). The first one retrieves all the pricing data needed for this
script to run (only needs to be run once), and the second one executes this
script with the parameters specified in the run_algorithm() call at the end
of the file:

catalyst ingest-exchange -x bitfinex -f minute

python simple_universe.py

"""
from datetime import timedelta

import numpy as np
import pandas as pd

from catalyst import run_algorithm
from catalyst.api import (symbols, )
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols


def initialize(context):
    context.i = -1  # minute counter
    context.exchange = list(context.exchanges.values())[0].name.lower()
    context.base_currency = list(context.exchanges.values())[0].base_currency.lower()


def handle_data(context, data):
    context.i += 1
    lookback_days = 7  # 7 days

    # current date & time in each iteration formatted into a string
    now = data.current_dt
    date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ')
    lookback_date = now - timedelta(days=lookback_days)
    # keep only the date as a string, discard the time
    lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0]

    one_day_in_minutes = 1440  # 60 * 24 assumes data_frequency='minute'
    # update universe everyday at midnight
    if not context.i % one_day_in_minutes:
        context.universe = universe(context, lookback_date, date)

    # get data every 30 minutes
    minutes = 30

    # get lookback_days of history data: that is 'lookback' number of bins
    lookback = int(one_day_in_minutes / minutes * lookback_days)
    if not context.i % minutes and context.universe:
        # we iterate for every pair in the current universe
        for coin in context.coins:
            pair = str(coin.symbol)

            # Get 30 minute interval OHLCV data. This is the standard data
            # required for candlestick or indicators/signals. Return Pandas
            # DataFrames. 30T means 30-minute re-sampling of one minute data.
            # Adjust it to your desired time interval as needed.
            opened = fill(data.history(coin,
                                       'open',
                                       bar_count=lookback,
                                       frequency='30T')).values
            high = fill(data.history(coin,
                                     'high',
                                     bar_count=lookback,
                                     frequency='30T')).values
            low = fill(data.history(coin,
                                    'low',
                                    bar_count=lookback,
                                    frequency='30T')).values
            close = fill(data.history(coin,
                                      'price',
                                      bar_count=lookback,
                                      frequency='30T')).values
            volume = fill(data.history(coin,
                                       'volume',
                                       bar_count=lookback,
                                       frequency='30T')).values

            # close[-1] is the last value in the set, which is the equivalent
            # to current price (as in the most recent value)
            # displays the minute price for each pair every 30 minutes
            print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},'
                  '\tV:{v}'.format(
                    now=now,
                    pair=pair,
                    o=opened[-1],
                    h=high[-1],
                    l=low[-1],
                    c=close[-1],
                    v=volume[-1],
                  ))

            # -------------------------------------------------------------
            # --------------- Insert Your Strategy Here -------------------
            # -------------------------------------------------------------


def analyze(context=None, results=None):
    pass


# Get the universe for a given exchange and a given base_currency market
# Example: Poloniex BTC Market
def universe(context, lookback_date, current_date):
    # get all the pairs for the given exchange
    json_symbols = get_exchange_symbols(context.exchange)
    # convert into a DataFrame for easier processing
    df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str)
    df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],
                                   axis=1)
    df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],
                                     axis=1)

    # Filter all the pairs to get only the ones for a given base_currency
    df = df[df['base_currency'] == context.base_currency]

    # Filter all pairs to ensure that pair existed in the current date range
    df = df[df.start_date < lookback_date]
    df = df[df.end_daily >= current_date]
    context.coins = symbols(*df.symbol)  # convert all the pairs to symbols

    return df.symbol.tolist()


# Replace all NA, NAN or infinite values with its nearest value
def fill(series):
    if isinstance(series, pd.Series):
        return series.replace([np.inf, -np.inf], np.nan).ffill().bfill()
    elif isinstance(series, np.ndarray):
        return pd.Series(series).replace(
                     [np.inf, -np.inf], np.nan
                    ).ffill().bfill().values
    else:
        return series


if __name__ == '__main__':
    start_date = pd.to_datetime('2017-11-10', utc=True)
    end_date = pd.to_datetime('2017-11-13', utc=True)

    performance = run_algorithm(start=start_date, end=end_date,
                                capital_base=100.0,  # amount of base_currency
                                initialize=initialize,
                                handle_data=handle_data,
                                analyze=analyze,
                                exchange_name='poloniex',
                                data_frequency='minute',
                                base_currency='btc',
                                live=False,
                                live_graph=False,
                                algo_namespace='simple_universe')
