""" This is algos.euler.transformer module.
    This module is responsible for transforming raw candle data into training
    samples usable to the Euler algorithm.
"""
import datetime
import decimal

from algos.euler.models import training_samples as ts
from core.models import instruments
from datasource.models import candles

TWO_PLACES = decimal.Decimal('0.01')


def extract_features(day_candle):
    """ Extract the features for the learning algorithm from a daily candle.
        The Features are:
            high_bid, low_bid, close_bid, open_ask, high_ask, low_ask,
            and close_ask (all relative to open_bid) in pips.

        Args:
            day_candle: candles.Candle object representing a daily candle.

        Returns:
            features: List of Decimals. The features described above, all in two
                decimal places.
    """
    multiplier = day_candle.instrument.multiplier
    features = [
        day_candle.high_bid,
        day_candle.low_bid,
        day_candle.close_bid,
        day_candle.open_ask,
        day_candle.high_ask,
        day_candle.low_ask,
        day_candle.close_ask,
    ]
    features = [multiplier * (x - day_candle.open_bid) for x in features]
    features = [decimal.Decimal(x).quantize(TWO_PLACES) for x in features]

    return features


def get_profitable_change(day_candle):
    """ Get the potential daily profitable price change in pips.
            If prices rise enough, we have: close_bid - open_ask (> 0), buy.
            If prices fall enough, we have: close_ask - open_bid (< 0), sell.
            if prices stay relatively still, we don't buy or sell. It's 0.

        Args:
            day_candle: candles.Candle object representing a daily candle.

        Returns:
            profitable_change: Decimal. The profitable rate change described
                above, in two decimal places.
    """
    multiplier = day_candle.instrument.multiplier
    change = 0
    if day_candle.close_bid > day_candle.open_ask:
        change = multiplier * (day_candle.close_bid - day_candle.open_ask)
    elif day_candle.close_ask < day_candle.open_bid:
        change = multiplier * (day_candle.close_ask - day_candle.open_bid)

    return decimal.Decimal(change).quantize(TWO_PLACES)


def build_sample_row(candle_previous, candle_next):
    """ Build one training sample from two consecutive days of candles.

        Args:
            candle_previous: candles.Candle object. Candle of first day.
            candle_next: candles.Candle object. Candle of second day.

        Returns:
            sample: TrainingSample object. One training sample for learning.
    """
    return ts.create_one(
        instrument=candle_next.instrument,
        date=candle_next.start_time.date() + datetime.timedelta(1),
        features=extract_features(candle_previous),
        target=get_profitable_change(candle_next))


def get_start_time(instrument):
    """ Get the start time for retrieving candles of the given instrument.
        This is determined by the last training sample in the database.

        Args:
            instrument: Instrument object. The given instrument.

        Returns:
            start_time: Datetime object. The datetime from which to query
                candles from to fill the rest of the training samples.
    """
    last_sample = ts.get_last(instrument)
    if last_sample is not None:
        start_date = last_sample.date - datetime.timedelta(1)
        return datetime.datetime.combine(start_date, datetime.time())

    return datetime.datetime(2005, 1, 1)


def run():
    """ Update the training samples in the database from the latest candles.
        This should be run daily to ensure the training set is up-to-date.

        Args:
            None.
    """
    all_new_samples = []
    for instrument in instruments.get_all():
        start_time = get_start_time(instrument)
        new_candles = candles.get_candles(
            instrument=instrument, start=start_time, order_by='start_time')
        for i in range(len(new_candles) - 1):
            all_new_samples.append(
                build_sample_row(new_candles[i], new_candles[i + 1]))

    ts.insert_many(all_new_samples)

[((12, 13, 12, 36), 'decimal.Decimal', 'decimal.Decimal', ({(12, 29, 12, 35): '"""0.01"""'}, {}), "('0.01')", False, 'import decimal\n'), ((95, 18, 95, 41), 'algos.euler.models.training_samples.get_last', 'ts.get_last', ({(95, 30, 95, 40): 'instrument'}, {}), '(instrument)', True, 'from algos.euler.models import training_samples as ts\n'), ((100, 11, 100, 40), 'datetime.datetime', 'datetime.datetime', ({(100, 29, 100, 33): '(2005)', (100, 35, 100, 36): '(1)', (100, 38, 100, 39): '(1)'}, {}), '(2005, 1, 1)', False, 'import datetime\n'), ((111, 22, 111, 43), 'core.models.instruments.get_all', 'instruments.get_all', ({}, {}), '()', False, 'from core.models import instruments\n'), ((119, 4, 119, 35), 'algos.euler.models.training_samples.insert_many', 'ts.insert_many', ({(119, 19, 119, 34): 'all_new_samples'}, {}), '(all_new_samples)', True, 'from algos.euler.models import training_samples as ts\n'), ((113, 22, 114, 75), 'datasource.models.candles.get_candles', 'candles.get_candles', (), '', False, 'from datasource.models import candles\n'), ((64, 11, 64, 34), 'decimal.Decimal', 'decimal.Decimal', ({(64, 27, 64, 33): 'change'}, {}), '(change)', False, 'import decimal\n'), ((97, 40, 97, 61), 'datetime.timedelta', 'datetime.timedelta', ({(97, 59, 97, 60): '(1)'}, {}), '(1)', False, 'import datetime\n'), ((98, 53, 98, 68), 'datetime.time', 'datetime.time', ({}, {}), '()', False, 'import datetime\n'), ((39, 16, 39, 34), 'decimal.Decimal', 'decimal.Decimal', ({(39, 32, 39, 33): 'x'}, {}), '(x)', False, 'import decimal\n'), ((79, 45, 79, 66), 'datetime.timedelta', 'datetime.timedelta', ({(79, 64, 79, 65): '(1)'}, {}), '(1)', False, 'import datetime\n')]