from alephnull import TradingAlgorithm
from alephnull.roll_method import roll
from alephnull.sources.futures_data_frame_source import FuturesDataFrameSource
%load_ext autoreload
%autoreload 2
import pandas as pd
from pandas import DataFrame, Series
import datetime as dt
import numpy as np
import string
Create some synthetic Futures Contracts
num_syms = 3
num_expirations = 3
expirations = 'F,G,H,J,K,M,N,Q,U,V,X,Z'.split(',')
fields = 'price, volume, open_interest'.split(', ')
syms = [''.join(np.random.choice(list(string.uppercase), np.random.randint(2,3)))
for x in xrange(num_syms)]
contracts = [(sym , np.random.choice(expirations) + str(np.random.randint(14,15)))
for x in xrange(num_expirations) for sym in syms]
cols = list(set([contract + (field,) for field in fields for contract in contracts]))
rands = np.random.standard_normal(size=(500, len(cols)))
now = dt.datetime.utcnow()
dates = pd.date_range(end= now, start= now - dt.timedelta(days = rands.shape[0]-1))
df_data = DataFrame(rands, index=dates, columns = pd.MultiIndex.from_tuples(cols)
).swapaxes(0,1).sort_index().swapaxes(0,1)
base = (100, 3200, 50)
ratios = [x + 1. / base[0] for x in base]
for col in cols:
if 'price' in col:
df_data[col].ix[0] = abs(df_data[col].ix[0] * base[0])
df_data[col].ix[1:] = abs(df_data[col].cumsum())
if 'volume' in col:
df_data[col].ix[0] = abs(df_data[col].ix[0] * base[1])
df_data[col].ix[1:] = df_data[col].cumsum()
df_data[col] = df_data[col].map(lambda x: int(x))
if 'open_interest' in col:
df_data[col].ix[0] = abs(df_data[col].ix[0] * base[2])
df_data[col].ix[1:] = df_data[col].cumsum()
df_data[col] = df_data[col].map(lambda x: int(x))
df_data = df_data.tz_localize('UTC')
Plot the Open Interest as a reference to ensure roll method is working below
[df_data.xs('open_interest', level=2, axis=1)[sym].plot(figsize(18,6)) for sym in syms]
Run a simple algo that buys 5 contracts. If successful we will have 5 contracts of each underlying in the front month throughout the backtest
class Bot(TradingAlgorithm):
def initialize(self, *args):
self.invested = False
@roll(lambda x: x[x['open_interest'] == x['open_interest'].max()])
def handle_data(self, data):
if not self.invested:
for sym in data.keys():
self.order((sym, data[sym]['contract']), 5)
self.invested = True
fut_data = FuturesDataFrameSource(df_data)
instance = Bot()
stats = instance.run(fut_data)
Positions at the end of the run seem to indicate that everything worked. Zero position amounts are from Back Months
positions = instance.perf_tracker.get_portfolio().positions
DataFrame({k:v.__dict__ for k,v in positions.iteritems()})
Validate that nothing strange happened by glossing through the position history and cross checking with the plots above
frames = {}
for x,y in stats.positions.map(lambda x:
DataFrame([Series(pos, name=pos['sid'])
for pos in x])).iteritems():
frames[x] = y.stack()
positions = pd.concat(frames, axis=1).T.drop('sid', level=1, axis=1)
positions
No gaps or flatlnes in the equity curve are also validating
stats.portfolio_value.plot()
Currently Roll Returns a dict vs BarData, this needs patched
from pandas import Timestamp
from alephnull.protocol import SIDData, BarData
bar = {
'FA': {'type': 4, 'price': 210.06995717015968, 'contract': 'Q14', 'datetime':
Timestamp('2012-10-31 16:33:30.959157', tz=None), 'volume': 508, 'sid': 'FA',
'source_id': 'FuturesDataFrameSource-aa7290ce07234a3a81d6ce626f4c9e58',
'dt': Timestamp('2012-10-31 16:33:30.959157', tz=None), 'open_interest': 52},
'WA': {'type': 4, 'price': 23.765399420437273, 'contract': 'F14', 'datetime':
Timestamp('2012-10-31 16:33:30.959157', tz=None), 'volume': 2427, 'sid': 'WA',
'source_id': 'FuturesDataFrameSource-aa7290ce07234a3a81d6ce626f4c9e58',
'dt': Timestamp('2012-10-31 16:33:30.959157', tz=None), 'open_interest': 60},
'DI': {'type': 4, 'price': 50.47594609660444, 'contract': 'M14', 'datetime':
Timestamp('2012-10-31 16:33:30.959157', tz=None), 'volume': 57, 'sid': 'DI',
'source_id': 'FuturesDataFrameSource-aa7290ce07234a3a81d6ce626f4c9e58', 'dt':
Timestamp('2012-10-31 16:33:30.959157', tz=None), 'open_interest': 49}
}
# {k:SIDData(v) for k,v in bar.iteritems()}
current_data = BarData()
current_data.__dict__['_data'].update({k:SIDData(v) for k,v in bar.iteritems()})
current_data.__dict__
Now that the above has been implemented in Roll
class BarData(TradingAlgorithm):
def initialize(self, *args):
self.counter = 0
@roll(lambda x: x[x['open_interest'] == x['open_interest'].max()])
def handle_data(self, data):
if self.counter < 10:
print data
self.counter +=1
futdata = FuturesDataFrameSource(df_data)
instance = BarData()
stats = instance.run(futdata)
Roll now returns a BarData Object!
class FixedRollAlgo(TradingAlgorithm):
@roll(lambda x: x[x['open_interest'] == x['open_interest'].max()])
def handle_data(self, data):
if np.random.randint(0,100) > 70:
for sym in data.keys():
# Now that this is fixed we can do . indexing
#eg (data[sym].contract)
self.order((sym, data[sym].contract),
1 * np.random.choice([-1.0, 1.0]))
futdata = FuturesDataFrameSource(df_data)
instance = FixedRollAlgo()
stats = instance.run(futdata)
Double Check Before Declaring Fixed
stats.portfolio_value.plot()
Equity Curve looks accurate
frames = {}
for x,y in stats.positions.map(lambda x:
DataFrame([Series(pos, name=pos['sid'])
for pos in x])).iteritems():
frames[x] = y.stack()
positions = pd.concat(frames, axis=1).T.drop('sid', level=1, axis=1)
positions
aside from the NaN's (Not sure if bug or if null positions result in NaNs), positions seem to be persistent through roll over