Introduction

In the previous Notebook, we create a very simple simulation taking only 1 Portfolio and using only the performance. The objective of this notebook is to create a more complete simulator able to take into consideration possible taxes, multiple portfolios and use the open price to buy and sell at closed price.

Loading Data

As we did previously, let's load the previous dataset with weekly prices

In [99]:
import time

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.dates as mdates
import matplotlib.mlab as mlab

import gc

import datetime

%matplotlib inline
In [100]:
df = pd.read_csv("F:/data/trading/stat_history.csv", header =[0, 1], index_col=[0, 1])
In [101]:
df.head()
Out[101]:
open close low high volume
min max mean min max mean min max mean min max mean min max mean sum
symbol date
AIA 2013-11-04 43.3307 43.3307 43.33070 43.5742 43.5742 43.574200 43.3307 43.3307 43.330700 43.6103 43.6103 43.61030 12326 12326 12326.00 12326
2013-11-11 42.3476 43.1052 42.87070 42.5550 43.1593 42.836240 42.2844 43.0884 42.694160 42.7345 43.2567 43.04332 12168 35407 23328.40 116642
2013-11-18 42.0103 44.3499 42.87540 42.2844 44.1425 43.009180 42.0083 44.0162 42.779400 42.2934 44.4023 43.13348 3991 47895 21017.20 105086
2013-11-25 43.5923 44.4131 44.02704 43.5742 44.2507 43.906160 43.5292 44.2417 43.760060 43.8178 44.4293 44.12590 6599 37123 23889.40 119447
2013-12-02 43.7186 44.4221 44.12220 43.9080 44.4582 44.183075 43.7186 44.3418 44.029975 43.9711 44.5123 44.30710 6229 18233 11868.75 47475

Model

First thing is to create a class which is a Portfolio. A portfolio have an initial value and will store the ETF and the performance. It's also this which will be taxed. I won't describe the complete code but it's more or less what it manages

In [102]:
class Portfolio():
    def __init__(self, amount, management_fees=0.5, arbitration_fees=0.5, verbose=0):
        self.init_amount = amount
        self.current_amount = amount
        self.open_date = None
        self.history = {}  # date : perfs
        self.current_ETF = None
        self.buy_perf = None
        self.current_perf = None
        self.management_fees = management_fees
        self.arbitration_fees = arbitration_fees
        self.verbose = verbose
        
    def apply_management_fees(self):
        self.current_amount *= (1 - self.management_fees/100)
        
    def apply_arbitration_fees(self):
        self.current_amount *= (1 - self.arbitration_fees/100)
        
    def sell(self):
        self.current_amount *= (self.current_perf/self.buy_perf)
        self.apply_arbitration_fees()
        if self.verbose == 1 :
            print("Selling {} @ {:.3f} (buy @ {:.3f})".format(self.current_ETF, self.current_perf, self.buy_perf))
        self.current_ETF = None
        self.buy_perf = None
        self.current_perf = None
    
    def buy(self, ETF, perf, date):
        if self.open_date is None:
            self.open_date = self.to_date(date)
        self.current_ETF = ETF
        self.buy_perf = perf
        self.current_perf = perf
        if self.verbose == 1 :
            print("{} - Buying {} @ {:.3f}".format(date, ETF, perf))
        
    def update(self, perf_day, freq):
        current_date = perf_day.name
        if self.current_ETF is not None:
            self.current_perf = perf_day["close"][self.current_ETF]
            self.history[current_date] = self.current_amount*self.current_perf/self.buy_perf
        
        if self.open_date is not None:
            delta = self.to_date(current_date) - self.open_date
            if 0 <= delta.days/7 % 52 < freq:
                self.apply_management_fees()
    
    def to_date(self, date, format_date="%Y-%m-%d"):
        if type(date) != "str":
            return date
        else:
            return datetime.datetime.strptime(date, format_date)
    
    def get_status(self):
        print("Current ETF : {}\
                \n\t Total Perf : {}\
                \n\t Buy Perf : {}\
                \n\t Current Perf : {}".format(self.current_ETF, 
                                              self.current_amount*self.current_perf/self.buy_perf,
                                              self.buy_perf,
                                              self.current_perf)
             )

The second thing is to create a class which is the model. It has to take as input the prevous dataframe and find the N most performant ETFs and place the order for each portfolios. To simplify the automation, the portfolios with the more money will buy the ETF with the best performance (if there is multiple portfolios). This class is quite long because it also allow to plot a dashboard of performances

In [103]:
class Agent:
    def __init__(self, 
                 df, 
                 n_ETF = 1, 
                 management_fees=0.5, 
                 arbitration_fees=0.5,
                 verbose=0):
        self.raw_df = df
        self.n_ETF = n_ETF
        self.portfolios = [Portfolio(1.0, management_fees, arbitration_fees, verbose) for _ in range(self.n_ETF)]
        self.preprocess_df()
        self.verbose=verbose
        gc.collect()
        
    def reset(self, n_ETF = 1, management_fees=0.5, arbitration_fees=0.5):
        self.portfolios = [Portfolio(1.0, management_fees, arbitration_fees, self.verbose) for _ in range(self.n_ETF)]
        self.n_ETF = n_ETF
        gc.collect()
        
    def preprocess_df(self):
        self.df = self.raw_df.iloc[:, self.raw_df.columns.get_level_values(1)=='mean']
        self.df.columns = self.df.columns.get_level_values(0)
        self.df = self.df.reset_index().pivot(index='date', columns='symbol')
        self.df.index = pd.to_datetime(self.df.index, format="%Y-%m-%d")
        temp0 = (self.df["high"] + self.df["low"])/2
        norm_val = temp0.apply(self.get_norm_vect, axis=0)
        temp0 = temp0.apply(self.normalize, axis=0)
        temp1 = self.df["open"]/norm_val
        temp2 = self.df["close"]/norm_val
        temp3 = self.df["low"]/norm_val
        temp4 = self.df["high"]/norm_val
        temp0.columns = pd.MultiIndex.from_product([['global_perf'], temp0.columns])
        temp1.columns = pd.MultiIndex.from_product([['open_perf'], temp1.columns])
        temp2.columns = pd.MultiIndex.from_product([['close_perf'], temp2.columns])
        temp3.columns = pd.MultiIndex.from_product([['low_perf'], temp3.columns])
        temp4.columns = pd.MultiIndex.from_product([['high_perf'], temp4.columns])
        self.df = pd.concat([self.df, temp0, temp1, temp2, temp3, temp4], axis=1)
        self.num_week = len(self.df)
        
    def compute_discounted_reward(self, x):
        gamma = 0.85
        value = 0
        p = x/x.shift(1)
        p = p.dropna()
        for i, val in enumerate(reversed(p)):
            if not np.isnan(val):
                value += (val-1) * gamma**i
            else:
                break
        return value
    
    def compute_trend(self, perfs, method):
        if method == "simple":
            return perfs.iloc[-1] / perfs.iloc[0]
        elif method == "discounted":
            return perfs.apply(self.compute_discounted_reward)
    
    def get_new_top_trends(self, sub_df, method):
        trend = self.compute_trend(sub_df["global_perf"], method)
        return trend.sort_values(ascending=False, na_position="last").iloc[:self.n_ETF]
        
    def simulate(self, freq = 4, period = 26, method="simple"):
        self.simulation_method = method
        for current_week in range(26, self.num_week, freq):  # start at 26 to have the same duration for each simulation
            current_ETF = set([x.current_ETF for x in self.portfolios if x.current_ETF is not None])
            start_week = current_week - period
            top_trend = self.get_new_top_trends(self.df.iloc[start_week:current_week], method)
            top_trend_idx = set(top_trend.index)
            for pf in self.portfolios:
                pf.update(self.df.iloc[current_week], freq)
            to_sell = current_ETF - top_trend_idx  # set operation 
            to_buy = top_trend_idx - current_ETF   # set operation 
            keep = top_trend_idx & current_ETF
            if self.verbose == 1:
                print("\nWeek {}\nSell {}\tBuy : {}\tKeep {}".format(current_week, to_sell, to_buy, keep))
            self.update_portfolio(self.df.iloc[current_week], to_buy, to_sell, top_trend)
            
    def update_portfolio(self, current_perf, ETF_to_buy, ETF_to_sell, perfs):
        self.portfolios.sort(key=lambda x:x.current_amount, reverse=True)
        for portfolio in self.portfolios:
            if portfolio.current_ETF in ETF_to_sell:
                portfolio.sell()
                
            if portfolio.current_ETF is None:
                date = current_perf.name
                for ETF, perf in perfs.items():
                    if ETF in ETF_to_buy:
                        portfolio.buy(ETF, current_perf["open"][ETF], date)
                        ETF_to_buy.remove(ETF)  
                        break
            
#             portfolio.get_status()

    def get_results(self):
        a = {"date":[], "perf":[], "portefolio":[]}
        for i, portfolio in enumerate(self.portfolios):
            for key, val in portfolio.history.items():
                a["date"].append(key)
                a["perf"].append(val)
                a["portefolio"].append(i)
        res = pd.DataFrame(a)
        res["date"] = pd.to_datetime(res["date"], format="%Y-%m-%d")
        return res.pivot(index='date', columns='portefolio', values="perf")
    
    
    def get_weekly_perf_by_portfolio(self, placeholder, global_perf, results):
        Y = global_perf
        X = results
        X_mean = results.mean(axis=1)
        X_mean.plot(legend=False, ax=placeholder, linewidth=8)
        X.plot(legend=False, ax=placeholder, linewidth=4)
        Y.plot(legend=False, ax=placeholder, linewidth=0.5)
        placeholder.set_xlabel("")
        placeholder.set_ylabel("Performance (%)")
        placeholder.set_title("Performances of ETFs and Momentum Trading")

    def get_weekly_perf_barplot(self, placeholder, results):
        results = results.mean(axis=1)
        results["perf_norm"] = results/results.shift(1)
        results["perf"] = (results["perf_norm"]-1)*100
        results = results.dropna()
        placeholder.bar(results["perf"].index, results["perf"], width=20)
        placeholder.set_ylabel("Performance (%)")
        placeholder.set_title("Performances per Period")
        placeholder.xaxis.set_major_locator(mdates.MonthLocator(interval=3))
        placeholder.xaxis.set_major_formatter(mdates.DateFormatter('%m-%Y'))
        placeholder.axhline(0, 0, 5, color="k", linestyle="--")

    def get_perf_distrib(self, placeholder, results):
        results = results.mean(axis=1).to_frame("avg")
        results["perf_norm"] = results["avg"]/results["avg"].shift(1)
        results["perf"] = (results["perf_norm"]-1)*100
        results = results.dropna()
        results["perf"].plot.hist(ax=placeholder, bins=50, legend=False)
        placeholder.set_ylabel("Frequence")
        placeholder.set_xlabel("Performance (%)")
        placeholder.set_title("Distribution of Performances")
        x_avg = results["perf"].mean()
        x_med = results["perf"].median()
        placeholder.axvline(x_avg, color="r", label = "Average Performance")
        placeholder.axvline(x_med, color="g", label = "Median Performance")
        ymin, ymax = placeholder.get_ylim()
        xmin, xmax = placeholder.get_xlim()
        v = (results["perf"]>0).mean()
        placeholder.text(xmin+0.8*(xmax-xmin), ymin+0.9*(ymax-ymin), "Ratio of Positives Periods : {:.2f}%".format(v*100), color='blue', fontweight='bold')
        placeholder.legend(loc = 2)

    def yearly_per_calendar_date(self, placeholder, results, period):
        results = results.mean(axis=1).to_frame("avg")
        results["perf_norm"] = results["avg"]/results["avg"].shift(1)
        results["perf"] = (results["perf_norm"]-1)*100
        results = results.dropna()
        results["year"] = results.index.year
        gb = results.groupby("year").agg({"perf_norm":['mean', "prod", "std"]})
        gb.columns = ["perf_mean", "perf_prod", "perf_std"]
        gb["perf_prod_norm"] = (gb["perf_prod"]-1)*100
        err = gb.perf_std.values
        gb["perf_prod_norm"].plot.bar(ax=placeholder, yerr=err*100)
        placeholder.set_xlabel("")
        placeholder.set_ylabel("Performance (%)")
        placeholder.set_title("Performance per Calendar Year")
        placeholder.axhline(0, 0, 5, color="k", linestyle="--")
        for i, v in enumerate(gb["perf_prod_norm"]):
            if v>0:
                y = v+2
            else:
                y = v-4
            placeholder.text(i+0.01, y, "{:.2f}%".format(v), color='blue', fontweight='bold')
        ymin, ymax = placeholder.get_ylim()
        xmin, xmax = placeholder.get_xlim()
        v = np.exp(np.log(results["perf_norm"].prod())/len(results))**(52/period)
        placeholder.text(xmin+0.7*(xmax-xmin), ymin+0.9*(ymax-ymin), "Eq. Yearly Perf : {:.2f}%".format((v-1)*100), color='blue', fontweight='bold')

    def yearly_per_duration(self, placeholder, results, period):
        results = results.mean(axis=1).to_frame("avg")
        results["perf_norm"] = results["avg"]/results["avg"].shift(1)
        results["perf"] = (results["perf_norm"]-1)*100
        results = results.dropna()
        results["year"] = results.index.year
        results = results.reset_index()
        gb = results.groupby(results.index//(52//period)).agg({"perf_norm":['mean', "prod", "std"]})
        gb.columns = ["perf_mean", "perf_prod", "perf_std"]
        gb["perf_prod_norm"] = (gb["perf_prod"]-1)*100
        err = gb.perf_std.values
        gb["perf_prod_norm"].plot.bar(ax=placeholder, yerr=err*100)
        placeholder.set_xticklabels(["year {}".format(i+1) for i in range(5)])
        placeholder.set_ylabel("Performance (%)")
        placeholder.set_title("Performance per Rolling Year")
        placeholder.axhline(0, 0, 5, color="k", linestyle="--")
        for i, v in enumerate(gb["perf_prod_norm"]):
            if v>0:
                y = v+2
            else:
                y = v-4
            placeholder.text(i+0.01, y, "{:.2f}%".format(v), color='blue', fontweight='bold')
        ymin, ymax = placeholder.get_ylim()
        xmin, xmax = placeholder.get_xlim()
        v = np.exp(np.log(results["perf_norm"].prod())/len(results))**(52/period)
        placeholder.text(xmin+0.7*(xmax-xmin), ymin+0.9*(ymax-ymin), "Eq. Yearly Perf : {:.2f}%".format((v-1)*100), color='blue', fontweight='bold')

    def plot(self, history_used, period, num_etf, method="show", path="F:/data/trading/models_perf/"):
        temp = self.get_results()
        glob_perf = self.df.iloc[:, self.df.columns.get_level_values(0)=='global_perf']
        fig = plt.figure(figsize=(20, 40))
        fig.suptitle('History {} - Period {} - #ETF {}'.format(history_used, period, num_etf), fontsize=20, y=0.90)

        gs = gridspec.GridSpec(4, 2, hspace=0.2)
        ax1 = plt.subplot(gs[0, :])
        ax2 = plt.subplot(gs[1, :])
        ax3 = plt.subplot(gs[2, :])
        ax4 = plt.subplot(gs[3, 0])
        ax5 = plt.subplot(gs[3, 1])

        self.get_weekly_perf_by_portfolio(ax1, glob_perf, temp)
        self.get_weekly_perf_barplot(ax2, temp)
        self.get_perf_distrib(ax3, temp)
        self.yearly_per_calendar_date(ax4, temp, period)
        self.yearly_per_duration(ax5, temp, period)

        if method=="show":
            plt.show()
        else:
            clear_name = "{}-{}-{}-{}".format(history_used, period, num_etf, self.simulation_method)
            hashname = abs(hash(clear_name))
            img_name = str(hashname) + ".png"
            plt.savefig(path + img_name, dpi=100, bbox_inches='tight')
            plt.close("all")
            return img_name

    def extract_summary_from_results(self, period):
        results = self.get_results()
        results = results.mean(axis=1)
        results["perf_norm"] = results/results.shift(1)
        results["perf"] = (results["perf_norm"]-1)*100
        results["perf"] = results["perf"].dropna()
        count = len(results["perf"])-1
        positive = (results["perf"]>0).mean()*100
        mean_perf = results["perf"].mean()
        std_perf = results["perf"].std()
        min_perf = results["perf"].min()
        max_perf = results["perf"].max()
        q0_1, q0_25, med_perf, q0_75, q0_9 = results["perf"].quantile([0.1, .25, .5, 0.75, 0.9]).tolist()
        flat_perf = (np.exp(np.log(results["perf_norm"].prod())/len(results))**(52/period) - 1)*100
        return count, positive, mean_perf, std_perf, min_perf, q0_1, q0_25, med_perf, q0_75, q0_9, max_perf, flat_perf
    
    @staticmethod
    def normalize(x):
        x0 = x[x.first_valid_index()]
        return x/x0
    
    @staticmethod
    def get_norm_vect(x):
        return x[x.first_valid_index()]

Aside - Discounted reward

The linear model has advantage in term of computation speed but we dont take into consideration the dynamic. For example:

The 2 ETFs have the same performance on a given duration. The problem is that the case 1 is in a bad trend compore to the case 2. To do so, we can apply a discounted reward to evaluate the performance. The idea is to give more weight to the last week compare to older one with a factor $\gamma$. The linear model is equivalement to $\gamma = 1$. The formula from Reinforcement learning is $ v = \sum_{i}\gamma^{i}*Perf(i) $. Now if we have a $\gamma == 0.85$

In [8]:
X = np.arange(26)
y = 0.85**X
print("Min {:.3f} - Max {:.3f}".format(min(y), max(y)))
plt.plot(X, y)
plt.ylim(0, 1)
plt.xlim(0,26)
plt.show()
Min 0.017 - Max 1.000

We can see that the last week will have a weight of 1 but the week 26 a weight of 0.017 (nearly nothing). As a result, a negativ result 26 weeks in the past won't affect the model compare to the same result in the last week

Simulation

Now we can 2 simulations, on with normal perfs and one with discounted reward method

In [10]:
num_etf = 1
freq = 4
history = 26

start = time.time()
mdl = Agent(df, n_ETF=num_etf, management_fees=0, arbitration_fees=0, verbose=1)
mdl.simulate(freq = freq, period = history)
print(time.time()-start)
Week 26
Sell set()	Buy : {'PPH'}	Keep set()
2014-05-05 00:00:00 - Buying PPH @ 55.069

Week 30
Sell set()	Buy : set()	Keep {'PPH'}

Week 34
Sell {'PPH'}	Buy : {'SILJ'}	Keep set()
Selling PPH @ 56.814 (buy @ 55.069)
2014-06-30 00:00:00 - Buying SILJ @ 13.060

Week 38
Sell {'SILJ'}	Buy : {'TUR'}	Keep set()
Selling SILJ @ 13.313 (buy @ 13.060)
2014-07-28 00:00:00 - Buying TUR @ 53.584

Week 42
Sell {'TUR'}	Buy : {'HEWG'}	Keep set()
Selling TUR @ 48.919 (buy @ 53.584)
2014-08-25 00:00:00 - Buying HEWG @ 20.520

Week 46
Sell {'HEWG'}	Buy : {'TUR'}	Keep set()
Selling HEWG @ 21.278 (buy @ 20.520)
2014-09-22 00:00:00 - Buying TUR @ 47.166

Week 50
Sell {'TUR'}	Buy : {'BBH'}	Keep set()
Selling TUR @ 44.935 (buy @ 47.166)
2014-10-20 00:00:00 - Buying BBH @ 98.169

Week 54
Sell set()	Buy : set()	Keep {'BBH'}

Week 58
Sell set()	Buy : set()	Keep {'BBH'}

Week 62
Sell set()	Buy : set()	Keep {'BBH'}

Week 66
Sell set()	Buy : set()	Keep {'BBH'}

Week 70
Sell set()	Buy : set()	Keep {'BBH'}

Week 74
Sell {'BBH'}	Buy : {'HEWG'}	Keep set()
Selling BBH @ 126.838 (buy @ 98.169)
2015-04-06 00:00:00 - Buying HEWG @ 25.993

Week 78
Sell {'HEWG'}	Buy : {'MCHI'}	Keep set()
Selling HEWG @ 24.993 (buy @ 25.993)
2015-05-04 00:00:00 - Buying MCHI @ 59.570

Week 82
Sell set()	Buy : set()	Keep {'MCHI'}

Week 86
Sell {'MCHI'}	Buy : {'QQQC'}	Keep set()
Selling MCHI @ 54.451 (buy @ 59.570)
2015-06-29 00:00:00 - Buying QQQC @ 25.555

Week 90
Sell {'QQQC'}	Buy : {'SYBT'}	Keep set()
Selling QQQC @ 22.215 (buy @ 25.555)
2015-07-27 00:00:00 - Buying SYBT @ 23.219

Week 94
Sell set()	Buy : set()	Keep {'SYBT'}

Week 98
Sell {'SYBT'}	Buy : {'KBWP'}	Keep set()
Selling SYBT @ 22.244 (buy @ 23.219)
2015-09-21 00:00:00 - Buying KBWP @ 43.220

Week 102
Sell {'KBWP'}	Buy : {'SYBT'}	Keep set()
Selling KBWP @ 44.290 (buy @ 43.220)
2015-10-19 00:00:00 - Buying SYBT @ 22.984

Week 106
Sell set()	Buy : set()	Keep {'SYBT'}

Week 110
Sell {'SYBT'}	Buy : {'PSCU'}	Keep set()
Selling SYBT @ 23.736 (buy @ 22.984)
2015-12-14 00:00:00 - Buying PSCU @ 36.136

Week 114
Sell set()	Buy : set()	Keep {'PSCU'}

Week 118
Sell set()	Buy : set()	Keep {'PSCU'}

Week 122
Sell {'PSCU'}	Buy : {'RING'}	Keep set()
Selling PSCU @ 39.129 (buy @ 36.136)
2016-03-07 00:00:00 - Buying RING @ 16.194

Week 126
Sell {'RING'}	Buy : {'SILJ'}	Keep set()
Selling RING @ 16.792 (buy @ 16.194)
2016-04-04 00:00:00 - Buying SILJ @ 8.618

Week 130
Sell set()	Buy : set()	Keep {'SILJ'}

Week 134
Sell set()	Buy : set()	Keep {'SILJ'}

Week 138
Sell set()	Buy : set()	Keep {'SILJ'}

Week 142
Sell set()	Buy : set()	Keep {'SILJ'}

Week 146
Sell set()	Buy : set()	Keep {'SILJ'}

Week 150
Sell set()	Buy : set()	Keep {'SILJ'}

Week 154
Sell {'SILJ'}	Buy : {'EWZS'}	Keep set()
Selling SILJ @ 13.528 (buy @ 8.618)
2016-10-17 00:00:00 - Buying EWZS @ 11.281

Week 158
Sell set()	Buy : set()	Keep {'EWZS'}

Week 162
Sell {'EWZS'}	Buy : {'SYBT'}	Keep set()
Selling EWZS @ 9.923 (buy @ 11.281)
2016-12-12 00:00:00 - Buying SYBT @ 42.227

Week 166
Sell set()	Buy : set()	Keep {'SYBT'}

Week 170
Sell set()	Buy : set()	Keep {'SYBT'}

Week 174
Sell set()	Buy : set()	Keep {'SYBT'}

Week 178
Sell {'SYBT'}	Buy : {'KBWB'}	Keep set()
Selling SYBT @ 38.936 (buy @ 42.227)
2017-04-03 00:00:00 - Buying KBWB @ 46.090

Week 182
Sell {'KBWB'}	Buy : {'FTXL'}	Keep set()
Selling KBWB @ 46.348 (buy @ 46.090)
2017-05-01 00:00:00 - Buying FTXL @ 25.155

Week 186
Sell {'FTXL'}	Buy : {'EMQQ'}	Keep set()
Selling FTXL @ 25.805 (buy @ 25.155)
2017-05-29 00:00:00 - Buying EMQQ @ 32.529

Week 190
Sell set()	Buy : set()	Keep {'EMQQ'}

Week 194
Sell {'EMQQ'}	Buy : {'TUR'}	Keep set()
Selling EMQQ @ 33.852 (buy @ 32.529)
2017-07-24 00:00:00 - Buying TUR @ 42.415

Week 198
Sell {'TUR'}	Buy : {'EMQQ'}	Keep set()
Selling TUR @ 42.725 (buy @ 42.415)
2017-08-21 00:00:00 - Buying EMQQ @ 34.580

Week 202
Sell {'EMQQ'}	Buy : {'TUR'}	Keep set()
Selling EMQQ @ 37.018 (buy @ 34.580)
2017-09-18 00:00:00 - Buying TUR @ 43.918

Week 206
Sell {'TUR'}	Buy : {'GAMR'}	Keep set()
Selling TUR @ 40.052 (buy @ 43.918)
2017-10-16 00:00:00 - Buying GAMR @ 44.629

Week 210
Sell {'GAMR'}	Buy : {'BOTZ'}	Keep set()
Selling GAMR @ 45.993 (buy @ 44.629)
2017-11-13 00:00:00 - Buying BOTZ @ 24.050

Week 214
Sell set()	Buy : set()	Keep {'BOTZ'}

Week 218
Sell set()	Buy : set()	Keep {'BOTZ'}

Week 222
Sell set()	Buy : set()	Keep {'BOTZ'}

Week 226
Sell {'BOTZ'}	Buy : {'IBUY'}	Keep set()
Selling BOTZ @ 24.418 (buy @ 24.050)
2018-03-05 00:00:00 - Buying IBUY @ 45.568

Week 230
Sell set()	Buy : set()	Keep {'IBUY'}

Week 234
Sell set()	Buy : set()	Keep {'IBUY'}

Week 238
Sell {'IBUY'}	Buy : {'PXI'}	Keep set()
Selling IBUY @ 46.635 (buy @ 45.568)
2018-05-28 00:00:00 - Buying PXI @ 44.867

Week 242
Sell {'PXI'}	Buy : {'PTH'}	Keep set()
Selling PXI @ 42.278 (buy @ 44.867)
2018-06-25 00:00:00 - Buying PTH @ 93.196

Week 246
Sell set()	Buy : set()	Keep {'PTH'}

Week 250
Sell {'PTH'}	Buy : {'JSML'}	Keep set()
Selling PTH @ 89.222 (buy @ 93.196)
2018-08-20 00:00:00 - Buying JSML @ 44.264

Week 254
Sell {'JSML'}	Buy : {'PTH'}	Keep set()
Selling JSML @ 46.124 (buy @ 44.264)
2018-09-17 00:00:00 - Buying PTH @ 97.746

Week 258
Sell {'PTH'}	Buy : {'FINX'}	Keep set()
Selling PTH @ 84.639 (buy @ 97.746)
2018-10-15 00:00:00 - Buying FINX @ 26.010
8.866689443588257
In [11]:
mdl.plot(history_used=history, period=freq, num_etf=num_etf, method="show")
In [12]:
num_etf = 1
freq = 4
history = 26

start = time.time()
mdl = Agent(df, n_ETF=num_etf, management_fees=0, arbitration_fees=0, verbose=1)
mdl.simulate(freq = freq, period = history, method="discounted")
print(time.time()-start)
Week 26
Sell set()	Buy : {'TUR'}	Keep set()
2014-05-05 00:00:00 - Buying TUR @ 47.183

Week 30
Sell set()	Buy : set()	Keep {'TUR'}

Week 34
Sell {'TUR'}	Buy : {'SILJ'}	Keep set()
Selling TUR @ 49.713 (buy @ 47.183)
2014-06-30 00:00:00 - Buying SILJ @ 13.060

Week 38
Sell set()	Buy : set()	Keep {'SILJ'}

Week 42
Sell {'SILJ'}	Buy : {'PGJ'}	Keep set()
Selling SILJ @ 12.687 (buy @ 13.060)
2014-08-25 00:00:00 - Buying PGJ @ 31.819

Week 46
Sell {'PGJ'}	Buy : {'BBH'}	Keep set()
Selling PGJ @ 29.980 (buy @ 31.819)
2014-09-22 00:00:00 - Buying BBH @ 104.425

Week 50
Sell {'BBH'}	Buy : {'TLT'}	Keep set()
Selling BBH @ 99.035 (buy @ 104.425)
2014-10-20 00:00:00 - Buying TLT @ 110.602

Week 54
Sell {'TLT'}	Buy : {'BBH'}	Keep set()
Selling TLT @ 107.610 (buy @ 110.602)
2014-11-17 00:00:00 - Buying BBH @ 113.757

Week 58
Sell set()	Buy : set()	Keep {'BBH'}

Week 62
Sell {'BBH'}	Buy : {'PSCU'}	Keep set()
Selling BBH @ 116.830 (buy @ 113.757)
2015-01-12 00:00:00 - Buying PSCU @ 34.125

Week 66
Sell {'PSCU'}	Buy : {'RING'}	Keep set()
Selling PSCU @ 35.011 (buy @ 34.125)
2015-02-09 00:00:00 - Buying RING @ 17.409

Week 70
Sell {'RING'}	Buy : {'HACK'}	Keep set()
Selling RING @ 15.436 (buy @ 17.409)
2015-03-09 00:00:00 - Buying HACK @ 28.550

Week 74
Sell {'HACK'}	Buy : {'HEWG'}	Keep set()
Selling HACK @ 27.483 (buy @ 28.550)
2015-04-06 00:00:00 - Buying HEWG @ 25.993

Week 78
Sell {'HEWG'}	Buy : {'QQQC'}	Keep set()
Selling HEWG @ 24.993 (buy @ 25.993)
2015-05-04 00:00:00 - Buying QQQC @ 26.264

Week 82
Sell set()	Buy : set()	Keep {'QQQC'}

Week 86
Sell {'QQQC'}	Buy : {'HACK'}	Keep set()
Selling QQQC @ 25.319 (buy @ 26.264)
2015-06-29 00:00:00 - Buying HACK @ 32.314

Week 90
Sell {'HACK'}	Buy : {'BBH'}	Keep set()
Selling HACK @ 30.896 (buy @ 32.314)
2015-07-27 00:00:00 - Buying BBH @ 139.997

Week 94
Sell {'BBH'}	Buy : {'PSL'}	Keep set()
Selling BBH @ 128.185 (buy @ 139.997)
2015-08-24 00:00:00 - Buying PSL @ 55.372

Week 98
Sell set()	Buy : set()	Keep {'PSL'}

Week 102
Sell {'PSL'}	Buy : {'SILJ'}	Keep set()
Selling PSL @ 54.284 (buy @ 55.372)
2015-10-19 00:00:00 - Buying SILJ @ 6.476

Week 106
Sell {'SILJ'}	Buy : {'PGJ'}	Keep set()
Selling SILJ @ 5.361 (buy @ 6.476)
2015-11-16 00:00:00 - Buying PGJ @ 29.562

Week 110
Sell set()	Buy : set()	Keep {'PGJ'}

Week 114
Sell {'PGJ'}	Buy : {'ENZL'}	Keep set()
Selling PGJ @ 28.968 (buy @ 29.562)
2016-01-11 00:00:00 - Buying ENZL @ 32.429

Week 118
Sell {'ENZL'}	Buy : {'RING'}	Keep set()
Selling ENZL @ 32.083 (buy @ 32.429)
2016-02-08 00:00:00 - Buying RING @ 12.842

Week 122
Sell set()	Buy : set()	Keep {'RING'}

Week 126
Sell {'RING'}	Buy : {'SILJ'}	Keep set()
Selling RING @ 16.792 (buy @ 12.842)
2016-04-04 00:00:00 - Buying SILJ @ 8.618

Week 130
Sell set()	Buy : set()	Keep {'SILJ'}

Week 134
Sell set()	Buy : set()	Keep {'SILJ'}

Week 138
Sell set()	Buy : set()	Keep {'SILJ'}

Week 142
Sell set()	Buy : set()	Keep {'SILJ'}

Week 146
Sell set()	Buy : set()	Keep {'SILJ'}

Week 150
Sell {'SILJ'}	Buy : {'QQQC'}	Keep set()
Selling SILJ @ 15.690 (buy @ 8.618)
2016-09-19 00:00:00 - Buying QQQC @ 22.288

Week 154
Sell {'QQQC'}	Buy : {'SYBT'}	Keep set()
Selling QQQC @ 22.304 (buy @ 22.288)
2016-10-17 00:00:00 - Buying SYBT @ 32.194

Week 158
Sell set()	Buy : set()	Keep {'SYBT'}

Week 162
Sell set()	Buy : set()	Keep {'SYBT'}

Week 166
Sell set()	Buy : set()	Keep {'SYBT'}

Week 170
Sell {'SYBT'}	Buy : {'EWZS'}	Keep set()
Selling SYBT @ 42.721 (buy @ 32.194)
2017-02-06 00:00:00 - Buying EWZS @ 12.493

Week 174
Sell set()	Buy : set()	Keep {'EWZS'}

Week 178
Sell {'EWZS'}	Buy : {'EMQQ'}	Keep set()
Selling EWZS @ 12.794 (buy @ 12.493)
2017-04-03 00:00:00 - Buying EMQQ @ 27.693

Week 182
Sell {'EMQQ'}	Buy : {'PSCU'}	Keep set()
Selling EMQQ @ 29.410 (buy @ 27.693)
2017-05-01 00:00:00 - Buying PSCU @ 50.749

Week 186
Sell {'PSCU'}	Buy : {'EMQQ'}	Keep set()
Selling PSCU @ 49.094 (buy @ 50.749)
2017-05-29 00:00:00 - Buying EMQQ @ 32.529

Week 190
Sell {'EMQQ'}	Buy : {'GAMR'}	Keep set()
Selling EMQQ @ 32.076 (buy @ 32.529)
2017-06-26 00:00:00 - Buying GAMR @ 41.694

Week 194
Sell {'GAMR'}	Buy : {'PTH'}	Keep set()
Selling GAMR @ 41.570 (buy @ 41.694)
2017-07-24 00:00:00 - Buying PTH @ 62.916

Week 198
Sell {'PTH'}	Buy : {'PGJ'}	Keep set()
Selling PTH @ 61.018 (buy @ 62.916)
2017-08-21 00:00:00 - Buying PGJ @ 41.429

Week 202
Sell {'PGJ'}	Buy : {'EWZS'}	Keep set()
Selling PGJ @ 43.903 (buy @ 41.429)
2017-09-18 00:00:00 - Buying EWZS @ 16.601

Week 206
Sell {'EWZS'}	Buy : {'CNCR'}	Keep set()
Selling EWZS @ 16.719 (buy @ 16.601)
2017-10-16 00:00:00 - Buying CNCR @ 27.059

Week 210
Sell {'CNCR'}	Buy : {'FTXL'}	Keep set()
Selling CNCR @ 24.209 (buy @ 27.059)
2017-11-13 00:00:00 - Buying FTXL @ 31.494

Week 214
Sell {'FTXL'}	Buy : {'ORG'}	Keep set()
Selling FTXL @ 29.470 (buy @ 31.494)
2017-12-11 00:00:00 - Buying ORG @ 33.466

Week 218
Sell {'ORG'}	Buy : {'PXI'}	Keep set()
Selling ORG @ 34.345 (buy @ 33.466)
2018-01-08 00:00:00 - Buying PXI @ 40.414

Week 222
Sell {'PXI'}	Buy : {'CNCR'}	Keep set()
Selling PXI @ 39.350 (buy @ 40.414)
2018-02-05 00:00:00 - Buying CNCR @ 28.136

Week 226
Sell set()	Buy : set()	Keep {'CNCR'}

Week 230
Sell set()	Buy : set()	Keep {'CNCR'}

Week 234
Sell {'CNCR'}	Buy : {'PXI'}	Keep set()
Selling CNCR @ 26.362 (buy @ 28.136)
2018-04-30 00:00:00 - Buying PXI @ 41.758

Week 238
Sell set()	Buy : set()	Keep {'PXI'}

Week 242
Sell {'PXI'}	Buy : {'IBUY'}	Keep set()
Selling PXI @ 42.278 (buy @ 41.758)
2018-06-25 00:00:00 - Buying IBUY @ 52.862

Week 246
Sell set()	Buy : set()	Keep {'IBUY'}

Week 250
Sell {'IBUY'}	Buy : {'JSML'}	Keep set()
Selling IBUY @ 51.650 (buy @ 52.862)
2018-08-20 00:00:00 - Buying JSML @ 44.264

Week 254
Sell {'JSML'}	Buy : {'PTH'}	Keep set()
Selling JSML @ 46.124 (buy @ 44.264)
2018-09-17 00:00:00 - Buying PTH @ 97.746

Week 258
Sell {'PTH'}	Buy : {'EWZS'}	Keep set()
Selling PTH @ 84.639 (buy @ 97.746)
2018-10-15 00:00:00 - Buying EWZS @ 13.512
5.158154249191284
In [13]:
mdl.plot(history_used=history, period=freq, num_etf=num_etf, method="show")