class BacktestingStrategy(object): def __init__(self, pair, capital, buy_strategy, sell_strategy, trading_fee=0, stop_loss=0): self.output = structlog.get_logger() self.prices = [] self.trades = [] self.sells = [] self.buys = [] self.max_trades_at_once = 1 self.indicators = StrategyAnalyzer() self.profit = 0 self.pair = pair self.reserve = capital self.buy_strategy = buy_strategy self.sell_stategy = sell_strategy self.trading_fee = trading_fee self.stop_loss = stop_loss ''' Runs our backtesting strategy on the set of backtesting candlestick data ''' def run(self, candlesticks): # Samples a random price within the range [candlestick.open, candlestick.close] sample_price = lambda op, close: random.uniform( min(op, close), max(op, close)) # The zero's are to take up space since our indicators require a full dataframe of OHLC datas self.prices = [[ 0, 0, 0, 0, sample_price(candle.open, candle.close), 0 ] for candle in candlesticks] # Hacky way to ensure indices match up :/ rsi = [None] * 14 macd = [None] * 33 nine_period = [None] * 9 fifteen_period = [None] * 15 nine_period_ema = [None] * 9 rsi.extend( self.indicators.analyze_rsi(self.prices, period_count=14, all_data=True)) macd.extend(self.indicators.analyze_macd(self.prices, all_data=True)) nine_period.extend( self.indicators.analyze_sma(self.prices, period_count=9, all_data=True)) fifteen_period.extend( self.indicators.analyze_sma(self.prices, period_count=15, all_data=True)) nine_period_ema.extend( self.indicators.analyze_ema(self.prices, period_count=9, all_data=True)) for i in range(len(self.prices)): # Get the (sampled) closing price and other indicators current_price = self.prices[i][4] current_rsi = rsi[i]["values"][0] if rsi[i] else None current_macd = macd[i]["values"][0] if macd[i] else None current_nine_period = nine_period[i]["values"][0] if nine_period[ i] else None current_fifteen_period = fifteen_period[i]["values"][ 0] if fifteen_period[i] else None current_nine_period_ema = nine_period_ema[i]["values"][ 0] if nine_period_ema[i] else None decision = Decision({ 'currentprice': current_price, 'rsi': current_rsi, 'sma9': current_nine_period, 'sma15': current_fifteen_period, 'ema9': current_nine_period_ema, 'macd': current_macd }) open_trades = [ trade for trade in self.trades if trade.status == 'OPEN' ] ### CHECK TO SEE IF WE CAN OPEN A BUY POSITION if len(open_trades) < self.max_trades_at_once: if decision.should_buy(self.buy_strategy): assert self.reserve > 0 self.buys.append((i, current_price)) new_trade = Trade(self.pair, current_price, self.reserve * (1 - self.trading_fee), stop_loss=self.stop_loss) self.reserve = 0 self.trades.append(new_trade) ### CHECK TO SEE IF WE NEED TO SELL ANY OPEN POSITIONS for trade in open_trades: if decision.should_sell(self.sell_stategy): self.sells.append((i, current_price)) profit, total = trade.close(current_price) self.profit += profit * (1 - self.trading_fee) self.reserve = total * (1 - self.trading_fee) ### CHECK TO SEE IF WE HAVE ACTIVATED A STOP LOSS for trade in self.trades: # Check our stop losses if trade.status == "OPEN" and trade.stop_loss and current_price < trade.stop_loss: profit, total = trade.close(current_price) self.sells.append((i, current_price)) self.profit += profit * (1 - self.trading_fee) self.reserve = total * (1 - self.trading_fee) def show_positions(self): for trade in self.trades: trade.show_trade()
class Chart(object): def __init__(self, pair, period, exchange_name, exchange_interface, start_time=time() - 2000000): self.pair = pair self.period = period self.start_time = start_time self.indicators = StrategyAnalyzer() self.data = [] # Query the data to fill our chart truncate it to 'length' elements rawdata = exchange_interface.get_historical_data( pair, exchange_name, period, start_time * 1000) for i in range(len(rawdata)): datum = rawdata[i] stick = Candlestick(open=datum[1], high=datum[2], low=datum[3], close=datum[4], price_average=(datum[2] + datum[3]) / 2.) stick.time = i self.data.append(stick) def get_points(self): return self.data ''' Returns the indicators specified in the **kwargs dictionary as a json-serializable dictionary ''' def get_indicators(self, **kwargs): # Indicators are hardcoded for now. Will be updated to accommodate variable-sized MA's response = { 'bollinger_upper': [], 'bollinger_lower': [], 'sma9': [], 'sma15': [], 'macd': [] } # Get closing historical datapoints closings = [[0, 0, 0, 0, x.close, 0] for x in self.data] # The 'bollinger' keyword argument takes in a period, i.e. bollinger=21 if "bollinger" in kwargs: period = kwargs["bollinger"] assert type(period) is int # Offset each band by "period" data points bbupper = [(i + period, datum["values"][0]) for i, datum in enumerate( self.indicators.analyze_bollinger_bands( closings, all_data=True))] bblower = [(i + period, datum["values"][2]) for i, datum in enumerate( self.indicators.analyze_bollinger_bands( closings, all_data=True))] response['bollinger_upper'] = bbupper response['bollinger_lower'] = bblower # The 'sma' keyword argument takes in a list of periods, i.e. sma=[9,15,21] if "sma" in kwargs: periods = kwargs["sma"] assert type(periods) is list for period in periods: # Offset each sma by "period" data points response['sma' + str(period)] = [ (i + period, datum["values"][0]) for i, datum in enumerate( self.indicators.analyze_sma( closings, period_count=period, all_data=True)) ] # To avoid overcomplicating things, the MACD just needs to be present in the dictionary with an arbitrary value if "macd" in kwargs: # 33 seems to be the magic offset...? response['macd'] = [ (i + 33, datum["values"][0]) for i, datum in enumerate( self.indicators.analyze_macd(closings, all_data=True)) ] return response ''' #################################################################### ### THIS FUNCTION IS DEPRECATED. PLOT IS NOW DISPLAYED ON THE UI ### #################################################################### Plots the specified indicators on a matplotlib plot ''' def plot_indicators(self, **kwargs): import numpy as np # Get closing historical datapoints closings = [[0, 0, 0, 0, x.close, 0] for x in self.data] plt.plot([x.close for x in self.data]) # The 'bollinger' keyword argument takes in a period, i.e. bollinger=21 if "bollinger" in kwargs: period = kwargs["bollinger"] assert type(period) is int bbupper = [ np.nan_to_num(datum["values"][0]) for datum in self.indicators.analyze_bollinger_bands( closings, all_data=True) ] bblower = [ np.nan_to_num(datum["values"][2]) for datum in self.indicators.analyze_bollinger_bands( closings, all_data=True) ] plt.plot(np.arange(period, len(closings)), bbupper[period:], 'g--') plt.plot(np.arange(period, len(closings)), bblower[period:], 'b--') # The 'sma' keyword argument takes in a list of periods, i.e. sma=[9,15,21] if "sma" in kwargs: periods = kwargs["sma"] assert type(periods) is list for period in periods: plt.plot([ np.nan_to_num(datum["values"][0]) for datum in self.indicators.analyze_sma( closings, period_count=period, all_data=True) ]) ''' Plots each buy trade as a green 'x', and each sell trade as a red 'x' ''' def plot_trades(self, buys, sells): for timestamp, price in buys: plt.plot(timestamp, price, 'gx') for timestamp, price in sells: plt.plot(timestamp, price, 'rx')