class Algorithm(object): """ Base class for trading algorithms. Inherit and overload initialize() and process_data(). Parameters ---------- id : str , required desc : str , default 'description n/a' delay_start : int, optional, default 0 used to delay the processing of data by the algorithm useful when an algorithm needs to access x data in past, which may or may not exist yet. i.e. eod[4] even though the data only goes back to eod[2]. Algorithm could use delay_start=4 to prevent any key errors being thrown Returns ------- n/a See Also -------- quant.examples.example_algo quant.example.sexample_suite Notes ----- self.delay_start is not needed majority of time. If SPY data goes back to 1993-1-1, and simulation starts on 1993-1-2 and algorithm checks eod[4].c > eod[0].c then an error would be thrown Examples -------- class MyAlgo(TradingAlgorithm): def initialize(amount, *args, **kwargs): self.shares = amount def process_data(env): eod = env['eod']['SPY'] self.order(eod, self.sharess) """ def __init__(self, *args, **kwargs): self.id = kwargs.get('id','') self.desc = kwargs.get('desc','description n/a') self.delay_start = kwargs.get('delay_start',0) self.ignore_old = kwargs.get('ignore_old',True) self.n = 0 self.order_mngr = Order_Manager(*args, **kwargs) self.stats_mngr = Statistics(*args, **kwargs) self.metrics = Environment() self.metrics.funcs = OrderedDict() self.now_dt = None self.last_now = None self.recorded_keys = None self.last_recorded_dt = None self.initialize(*args, **kwargs) def initialize_recorder(self, recorded_vars, cache=False, fn=''): """ Initializes recording functionality for algorithm. Parameters ---------- recorded_vars : list or dict , required use a list to initialize record() from algorithm.initialize() The list should contain keys of the variables to store in the order they should be listed in the record file where they are used as headers. Use a dict from within algorithm.process_data() where values are keyed to the same key names used in initialization. cache : bool, optional, default=False used to determine if recorded variables should be stored in history Maintaining a history of recorded variables is discouraged as it uses a lot of memory and is not necessary. If you absolutely need to keep track of a variable in history, use a metric fn : string, optional, default='' filename where recorded variables should be stored Returns ------- sets up self.recorded_keys and self.recorded and the output file See Also -------- n/a Notes ----- n/a Examples -------- class MyAlgo(TradingAlgorithm): def initialize(amount, *args, **kwargs): self.record(['gap_size', 'open_price', 'action']) self.initialize_recorder(['gap_size', 'open_price', 'action'], cache=False, fn='myDir/records.csv') """ self.recorded = OrderedDict() self.record_fn = fn if fn != '' else self.id+ '_record.csv' with open(self.record_fn, 'w') as f: f.write('Date,'+','.join(recorded_vars)+'\n') self.recorded_keys = recorded_vars self.cache_recorded = cache def update_strat(self, env, *args, **kwargs): self.now_dt = env.now_dt if self.n < self.delay_start: self.last_now = env.now_dt return self.order_mngr.update(env) try: self.process_data(env) except Date_Not_In_History_Error as e: if self.ignore_old: return else: raise e self.last_now = env.now_dt self.stats_mngr.update(env,self.order_mngr.closed_pos[self.stats_mngr.n:]) if self.recorded_keys != None: self.record({}) def add_metric(self, metrics): if type(metrics) != list: metrics = [metrics] for m in metrics: self.metrics.funcs[m.id] = m self.metrics.add_data(m.id, OrderedDict()) def update_metrics(self, env): self.n += 1 self.metrics.update(env.now_dt) for k,m in self.metrics.funcs.items(): self.metrics.data[m.id][env.now_dt] = m.update(env) def order(self, sid, shares, price, **kwargs): """ API front end order_processer for quant.finances.algorith.process_data this is only order method a user is allowed to access """ self.order_mngr.order(sid, shares, price, **kwargs) def record(self, recorded_vars): """ Adds recording functionality to algorithms. Can record any number of self defined variables. Can be called anywhere within algorithm All variables will be stored according to date they were generated. Parameters ---------- recorded_vars : list or dict , required Use a dict from within algorithm.process_data() where values are keyed to the same key names used in initialization. Returns ------- saves all recorded values to self.recorded, a dict keyed by datetime. self.recorded is cleared out at each new day based on self.cache_recorded Appends all recorded values in a csv file format to self.recorded_fn See Also -------- n/a Notes ----- Examples -------- class MyAlgo(TradingAlgorithm): def initialize(amount, *args, **kwargs): self.initialize_recorder(['gap_size', 'open_price', 'action'], False, fn='mydir/record.csv') def process_data(env): eod = env['eod']['SPY'] self.record({'gap_size': eod.o - eod[1].c, 'open_price': eod.o}) self.record({'action': 'buy'}) """ if len(recorded_vars) == 0 and self.last_recorded_dt in self.recorded: f = open(self.record_fn, 'a') f.write('%s,'%(self.last_recorded_dt)) for k in self.recorded_keys: f.write('%s,' % self.recorded[self.last_recorded_dt].get(k,'')) f.write('\n') f.close() if not self.cache_recorded: self.recorded.pop(self.last_recorded_dt) self.last_recorded_dt = None elif len(recorded_vars) == 0: return else: if self.now_dt not in self.recorded: self.recorded[self.now_dt] = OrderedDict() for k, v in recorded_vars.items(): self.recorded[self.now_dt][k] = v self.last_recorded_dt = self.now_dt def initialize(self, *args, **kwargs): """ All Algorithm administrative actions take place here. An example of things that should be done in initiliaze() are but not limited to: self.id = 'algorithm_title self.desc = 'algorithm description' self.add_metric() initialize_recorder() self.delay_start = 4 Parameters ---------- *args / **kwargs : any , optional passed down from simulator Returns ------- n/a See Also -------- n/a Notes ----- n/a Examples -------- class MyAlgo(TradingAlgorithm): def initialize(amount, *args, **kwargs): self.id = 'myalgo' self.desc = 'My super awesome algorithm' self.add_metric(Min(id='8daylow',window=8,func=lambda env: env.c),['SPY']) self.delay_start = 3 self.initialize_recorder(['gap_size', 'open_price', 'action']) """ pass def process_data(self, env, *args, **kwargs): pass
class Simulator(object): """ Simulator for backtesting Parameters ---------- start_dt : dt.datetime, required end_dt : dt.datetime, optional, default now calendar : str (data_source.id) or iterable, optioanl, default 'eod' this determines the dates to run the simulation on. If a type(str) is used it should exist as a data_source. It will use the dates that exist for the data_source. Returns ------- n/a See Also -------- n/a Notes ----- n/a Examples -------- n/a """ def __init__(self, start_dt, end_dt=dt.datetime.now(), *args, **kwargs): self.start_dt = start_dt self.end_dt = end_dt self.algos = [] self.calendar = kwargs.get('calendar', None) self.liquidate_all = kwargs.get('liquidate_all', True) self.env = Environment() def add_algo(self, algos): if type(algos) != list: algos = [algos] self.algos += algos def add_data(self, data_list): if type(data_list) != list: data_list = [data_list] for data_tuple in data_list: sid = data_tuple[0] data = data_tuple[1] self.env.add_data(sid, data) def set_calendar(self, calendar=None): """ method to set the master calendar used in the simulation This can be any custom calendar: Set trading days, business days, custom days like FOMC meetings, holidays Parameters ---------- calendar : list, required Returns ------- sets self.calendar and env.calendar See Also -------- n/a Notes ----- n/a Examples -------- # Restrict calendar to all the days SPY traded data_source = YEOD_Source(DATA+'/eod_data/') sim = Simulator(start_dt, end_dt) sim.set_calendar(data_source.load('SPY').keys()) """ self.calendar = calendar def liquidate(self): for algo in self.algos: algo.order_mngr.logic_order('close_all') algo.stats_mngr.update( self.env, algo.order_mngr.closed_pos[algo.stats_mngr.n:]) def run(self, driver='eod'): for now in self.calendar: if now > self.end_dt: break self.env.update(now) for algo in self.algos: algo.update_metrics(self.env) if self.start_dt <= now <= self.end_dt: algo.update_strat(self.env) if self.liquidate_all: self.liquidate()
class Simulator(object): """ Simulator for backtesting Parameters ---------- start_dt : dt.datetime, required end_dt : dt.datetime, optional, default now calendar : str (data_source.id) or iterable, optioanl, default 'eod' this determines the dates to run the simulation on. If a type(str) is used it should exist as a data_source. It will use the dates that exist for the data_source. Returns ------- n/a See Also -------- n/a Notes ----- n/a Examples -------- n/a """ def __init__(self, start_dt, end_dt=dt.datetime.now(), *args, **kwargs): self.start_dt = start_dt self.end_dt = end_dt self.algos = [] self.calendar = kwargs.get('calendar', None) self.liquidate_all = kwargs.get('liquidate_all', True) self.env = Environment() def add_algo(self, algos): if type(algos) != list: algos = [algos] self.algos += algos def add_data(self, data_list): if type(data_list) != list: data_list = [data_list] for data_tuple in data_list: sid = data_tuple[0] data = data_tuple[1] self.env.add_data(sid, data) def set_calendar(self, calendar=None): """ method to set the master calendar used in the simulation This can be any custom calendar: Set trading days, business days, custom days like FOMC meetings, holidays Parameters ---------- calendar : list, required Returns ------- sets self.calendar and env.calendar See Also -------- n/a Notes ----- n/a Examples -------- # Restrict calendar to all the days SPY traded data_source = YEOD_Source(DATA+'/eod_data/') sim = Simulator(start_dt, end_dt) sim.set_calendar(data_source.load('SPY').keys()) """ self.calendar = calendar def liquidate(self): for algo in self.algos: algo.order_mngr.logic_order('close_all') algo.stats_mngr.update(self.env,algo.order_mngr.closed_pos[algo.stats_mngr.n:]) def run(self,driver='eod'): for now in self.calendar: if now > self.end_dt: break self.env.update(now) for algo in self.algos: algo.update_metrics(self.env) if self.start_dt <= now <= self.end_dt: algo.update_strat(self.env) if self.liquidate_all: self.liquidate()