Overall Statistics
'''
    Ostirion Linear Controller BTC Price Demostration
    version 1.0
    Copyright (C) 2021  Ostirion

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
    
    Contact: www.ostirion.net/contact
'''


import numpy as np
import pandas as pd


class CryptoController(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2020, 1, 1)
        self.SetEndDate(2021, 1, 25)
        self.SetCash(1000000)
        self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin)

        res = Resolution.Hour
        self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel())
        self.Settings.RebalancePortfolioOnInsightChanges = True
        self.SetExecution(ImmediateExecutionModel())
        self.UniverseSettings.Resolution = res

        # Crypto to trade:
        crypto = ['BTCUSD']

        # K-Gain parameter, 4 as stated in the paper:
        try:
            self.K = float(self.GetParameter("K"))
        except:
            self.Debug('No K-gain parameter detected. Using default')
            self.K = float(4)

        # Create the symbols:
        self.crypto = [self.AddCrypto(ticker, res).Symbol for ticker in crypto]
        self.AddAlpha(CryptoControl(self.crypto, self.K))


class CryptoControl(AlphaModel):


    def __init__(self,
                 crypto,
                 K):

        self.Name = 'Crypto Controller'
        self.crypto = crypto
        self.K = K
        self.total_assets = len(self.crypto)
        self.init = False

        # Dictionaries for data:
        self.changes = {}
        self.loads = {}
        self.gains = {}
        
        # Low pass filter for very low weights, positions below
        # epsilon (0.005%) are set to 0 to avoid insights
        # that would result in no trade:
        self.epsilon = 0.005
        
        # Subsampler cycle of insights, skipping every square root
        # of the gain to allow intermediate rebalancing.
        self.subsampler = int(np.sqrt(int(self.K)))
        self.sample = 0

    def Update(self, algorithm, data):
        
        # Skip if no data in slice and clear insights:
        if not data:
            algorithm.Debug('No data. Skipping period.')
            return []
        
        self.sample += 1
        insights = []

        # Initialize the rate of change indicator,
        # setting the defaults for the first value.
        if not self.init:
            request_data = 120
            
            for symbol in self.crypto:
                h = algorithm.History
                self.loads[symbol] = (self.K*h(symbol, request_data, Resolution.Hour).loc[symbol]['close']).pct_change().sum()
                self.changes[symbol] = algorithm.ROC(symbol, 1)
            initial_load = sum(list(self.loads.values()))
            self.loads = {k:v/initial_load for k,v in self.loads.items()}
            self.init = True

        # Compute the gain g for each crypto symbol:
        for symbol in self.crypto:
            self.gains[symbol] = self.changes[symbol].Current.Value*self.K
            self.loads[symbol] = min(max(self.loads[symbol] + self.gains[symbol], 0), 1)
        
        # Subsample to avoid sync with rebalance and insight periods.
        if self.sample % self.subsampler != 0: return[]

        # Obtain insight time delta and magnitudes.
        t_delta = timedelta(hours=self.subsampler)
        magnitude_values = np.array(list(self.gains.values()))
        magnitude = abs(np.mean(magnitude_values))*100
        

        for symbol in self.crypto:
            w = self.loads[symbol] 
            g = self.gains[symbol]
            if w <= self.epsilon: continue
            if abs(g) <= self.epsilon/10000: continue

            insights.append(Insight(symbol, t_delta,
                                    InsightType.Price,
                                    InsightDirection.Up,
                                    magnitude, int(True),
                                    self.Name, w))
                                    

        return insights
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns


def plot_df(df, color='blue', size=(16, 7), legend='Close Price', y_label='Price in USD', title=None, kind='line'):
    plt.style.use('dark_background')
    plt.rcParams["figure.figsize"] = size
    ax = df.plot(kind=kind)
    plt.title(title)
    plt.ylabel(y_label)
    x = 0.1
    y = 0.4
    plt.text(x, y, 'www.ostirion.net', fontsize=15, transform=ax.transAxes)
    plt.legend(ncol=int(len(df.columns) / 2))
    date_form = mdates.DateFormatter("%m-%Y")
    plt.xticks(rotation=45);
    plt.show()
    
def plot_corr_hm(df, title='Title', size=(16, 7), annot = True):
    corr = df.corr()
    plt.style.use('dark_background')
    plt.rcParams["figure.figsize"] = size
    mask = np.triu(np.ones_like(corr, dtype=bool))
    cmap = sns.color_palette("RdBu")
    ax = sns.heatmap(corr, mask=mask, vmax=.3, center=0, cmap=cmap, annot=annot,
                     square=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
    ax.set_title(title)
    plt.setp(ax.get_yticklabels(), rotation=0);
    plt.setp(ax.get_xticklabels(), rotation=90);
    plt.show()

def plot_cm(df, title='Title', size=(16,7)):
    plt.style.use('dark_background')
    plt.rcParams["figure.figsize"] = size
    cmap = sns.color_palette("Blues")
    ax = sns.heatmap(df, cmap=cmap, annot=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
    ax.set_title(title)
    plt.xlabel('Predicted')
    plt.ylabel('True')
    plt.setp(ax.get_xticklabels(), rotation=0);

def plot_hm(df, title='Title', size=(16, 7), annot = True, x_rot=90):

    plt.style.use('dark_background')
    plt.rcParams["figure.figsize"] = size
    
    cmap = sns.color_palette("RdBu")
    ax = sns.heatmap(df, vmax=.3, center=0, cmap=cmap, annot=annot,
                     square=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
    ax.set_title(title)
    plt.setp(ax.get_yticklabels(), rotation=0);
    plt.setp(ax.get_xticklabels(), rotation=x_rot);
    plt.show()