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 __init__(self, config, exchange_interface, notifier): """Initializes DefaultBehaviour class. Args: indicator_conf (dict): A dictionary of configuration for this analyzer. exchange_interface (ExchangeInterface): Instance of the ExchangeInterface class for making exchange queries. notifier (Notifier): Instance of the notifier class for informing a user when a threshold has been crossed. """ self.logger = structlog.get_logger() self.indicator_conf = config.indicators self.informant_conf = config.informants self.crossover_conf = config.crossovers self.exchange_interface = exchange_interface self.strategy_analyzer = StrategyAnalyzer() self.notifier = notifier self.all_historical_data = dict() output_interface = Output() self.output = output_interface.dispatcher self.enable_charts = config.settings['enable_charts'] self.timezone = config.settings['timezone']
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(exchange_interface=None) 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
def __configure_simple_bot(self, behaviour_config): """Configures and returns the bot behaviour class. Args: behaviour_config (dict): A dictionary of configuration values pertaining to the behaviour. Returns: SimpleBotBehaviour: A class of functionality for the rsi bot behaviour. """ exchange_interface = ExchangeInterface(self.config.exchanges) strategy_analyzer = StrategyAnalyzer() notifier = Notifier(self.config.notifiers) db_handler = DatabaseHandler(self.config.database) behaviour = SimpleBotBehaviour( behaviour_config, exchange_interface, strategy_analyzer, notifier, db_handler ) return behaviour
def __configure_default(self, behaviour_config): """Configures and returns the default behaviour class. Args: behaviour_config (dict): A dictionary of configuration values pertaining to the behaviour. Returns: DefaultBehaviour: A class of functionality for the default behaviour. """ exchange_interface = ExchangeInterface(self.config.exchanges) strategy_analyzer = StrategyAnalyzer() notifier = Notifier(self.config.notifiers) behaviour = DefaultBehaviour( behaviour_config, exchange_interface, strategy_analyzer, notifier ) return behaviour
def __init__(self, pair, period, exchange_interface): self.pair = pair self.indicators = StrategyAnalyzer() self.data = [] # Query the data to fill our chart truncate it to 'length' elements raw_data = exchange_interface.get_historical_data( pair, interval=Chart.period_to_integer(period)) for datum in raw_data: stick = Candlestick(time=datum[0], open=datum[1], high=datum[2], low=datum[3], close=datum[4], price_average=(datum[2] + datum[3]) / 2.) self.data.append(stick)
def __init__(self, pair, capital, buy_strategy, sell_strategy, trading_fee=0, stop_loss=0): 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
def configure_rsi_bot(self, behaviour_config): exchange_interface = ExchangeInterface( self.config.fetch_exchange_config()) strategy_analyzer = StrategyAnalyzer(exchange_interface) notifier = Notifier(self.config.fetch_notifier_config()) db_handler = DatabaseHandler(self.config.fetch_database_config()) behaviour = RSIBot(behaviour_config, exchange_interface, strategy_analyzer, notifier, db_handler) return behaviour
def configure_default(self, behaviour_config): exchange_interface = ExchangeInterface( self.config.fetch_exchange_config()) strategy_analyzer = StrategyAnalyzer(exchange_interface) notifier = Notifier(self.config.fetch_notifier_config()) behaviour = DefaultBehaviour(behaviour_config, exchange_interface, strategy_analyzer, notifier) return behaviour
def main(): # Load settings and create the config object secrets = {} if os.path.isfile('secrets.json'): secrets = json.load(open('secrets.json')) config = json.load(open('default-config.json')) config.update(secrets) config['settings']['market_pairs'] = os.environ.get('MARKET_PAIRS', config['settings']['market_pairs']) config['settings']['loglevel'] = os.environ.get('LOGLEVEL', config['settings']['loglevel']) config['settings']['app_mode'] = os.environ.get('APP_MODE', config['settings']['app_mode']) config['exchanges']['bittrex']['required']['key'] = os.environ.get('BITTREX_KEY', config['exchanges']['bittrex']['required']['key']) config['exchanges']['bittrex']['required']['secret'] = os.environ.get('BITTREX_SECRET', config['exchanges']['bittrex']['required']['secret']) config['notifiers']['twilio']['required']['key'] = os.environ.get('TWILIO_KEY', config['notifiers']['twilio']['required']['key']) config['notifiers']['twilio']['required']['secret'] = os.environ.get('TWILIO_SECRET', config['notifiers']['twilio']['required']['secret']) config['notifiers']['twilio']['required']['sender_number'] = os.environ.get('TWILIO_SENDER_NUMBER', config['notifiers']['twilio']['required']['sender_number']) config['notifiers']['twilio']['required']['receiver_number'] = os.environ.get('TWILIO_RECEIVER_NUMBER', config['notifiers']['twilio']['required']['receiver_number']) config['notifiers']['gmail']['required']['username'] = os.environ.get('GMAIL_USERNAME', config['notifiers']['gmail']['required']['username']) config['notifiers']['gmail']['required']['password'] = os.environ.get('GMAIL_PASSWORD', config['notifiers']['gmail']['required']['password']) config['notifiers']['gmail']['required']['destination_emails'] = os.environ.get('GMAIL_DESTINATION_EMAILS', config['notifiers']['gmail']['required']['destination_emails']) # Set up logger logs.configure_logging(config['settings']['loglevel'], config['settings']['app_mode']) logger = structlog.get_logger() exchange_interface = ExchangeInterface(config) strategy_analyzer = StrategyAnalyzer(config) notifier = Notifier(config) # The coin pairs coin_pairs = [] if config['settings']['market_pairs']: coin_pairs = config['settings']['market_pairs'].translate(str.maketrans('', '', whitespace)).split(",") else: user_markets = exchange_interface.get_user_markets() for user_market in user_markets['info']: if 'BTC' in user_market['Currency']: continue market_pair = user_market['Currency'] + '/BTC' coin_pairs.append(market_pair) logger.debug(coin_pairs) while True: get_signal(coin_pairs, strategy_analyzer, notifier)
def __init__(self, config, exchange_interface, notifier): """Initializes DefaultBehaviour class. Args: indicator_conf (dict): A dictionary of configuration for this analyzer. exchange_interface (ExchangeInterface): Instance of the ExchangeInterface class for making exchange queries. notifier (Notifier): Instance of the notifier class for informing a user when a threshold has been crossed. """ self.logger = structlog.get_logger() self.indicator_conf = config.indicators self.informant_conf = config.informants self.crossover_conf = config.crossovers self.exchange_interface = exchange_interface self.strategy_analyzer = StrategyAnalyzer() self.notifier = notifier output_interface = Output() self.output = output_interface.dispatcher
def main(): # Load settings and create the config object config = conf.Configuration() settings = config.fetch_settings() exchange_config = config.fetch_exchange_config() notifier_config = config.fetch_notifier_config() behaviour_config = config.fetch_behaviour_config() # Set up logger logs.configure_logging(settings['loglevel'], settings['app_mode']) exchange_interface = ExchangeInterface(exchange_config) strategy_analyzer = StrategyAnalyzer(exchange_interface) notifier = Notifier(notifier_config) behaviour_manager = Behaviour(behaviour_config) behaviour = behaviour_manager.get_behaviour(settings['selected_task']) behaviour.run(settings['symbol_pairs'], settings['update_interval'], exchange_interface, strategy_analyzer, notifier)
def __configure_rsi_bot(self, behaviour_config): """Configures and returns the rsi bot behaviour class. Args: behaviour_config (dict): A dictionary of configuration values pertaining to the behaviour. Returns: RSIBot: A class of functionality for the rsi bot behaviour. """ exchange_interface = ExchangeInterface( self.config.get_exchange_config()) strategy_analyzer = StrategyAnalyzer(exchange_interface) notifier = Notifier(self.config.get_notifier_config()) db_handler = DatabaseHandler(self.config.get_database_config()) behaviour = RSIBot(behaviour_config, exchange_interface, strategy_analyzer, notifier, db_handler) return behaviour
def main(): """Initializes the application """ # Load settings and create the config object config = Configuration() settings = config.settings # Set up logger logs.configure_logging(settings['log_level'], settings['log_mode']) logger = structlog.get_logger() # Configure and run configured behaviour. exchange_interface = ExchangeInterface(config.exchanges) strategy_analyzer = StrategyAnalyzer() notifier = Notifier(config.notifiers) behaviour = Behaviour(config.behaviour, exchange_interface, strategy_analyzer, notifier) while True: behaviour.run(settings['market_pairs'], settings['output_mode']) logger.info("Sleeping for %s seconds", settings['update_interval']) time.sleep(settings['update_interval'])
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': [] } # 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)) ] 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')
def main(): # Load settings and create the config object secrets = {} if os.path.isfile('secrets.json'): secrets = json.load(open('secrets.json')) config = json.load(open('default-config.json')) config.update(secrets) config['settings']['market_pairs'] = os.environ.get( 'MARKET_PAIRS', config['settings']['market_pairs']) config['settings']['loglevel'] = os.environ.get('LOGLEVEL', logging.INFO) config['exchanges']['bittrex']['required']['key'] = os.environ.get( 'BITTREX_KEY', config['exchanges']['bittrex']['required']['key']) config['exchanges']['bittrex']['required']['secret'] = os.environ.get( 'BITTREX_SECRET', config['exchanges']['bittrex']['required']['secret']) config['notifiers']['twilio']['required']['key'] = os.environ.get( 'TWILIO_KEY', config['notifiers']['twilio']['required']['key']) config['notifiers']['twilio']['required']['secret'] = os.environ.get( 'TWILIO_SECRET', config['notifiers']['twilio']['required']['secret']) config['notifiers']['twilio']['required'][ 'sender_number'] = os.environ.get( 'TWILIO_SENDER_NUMBER', config['notifiers']['twilio']['required']['sender_number']) config['notifiers']['twilio']['required'][ 'receiver_number'] = os.environ.get( 'TWILIO_RECEIVER_NUMBER', config['notifiers']['twilio']['required']['receiver_number']) config['notifiers']['gmail']['required']['username'] = os.environ.get( 'GMAIL_USERNAME', config['notifiers']['gmail']['required']['username']) config['notifiers']['gmail']['required']['password'] = os.environ.get( 'GMAIL_PASSWORD', config['notifiers']['gmail']['required']['password']) config['notifiers']['gmail']['required'][ 'destination_emails'] = os.environ.get( 'GMAIL_DESTINATION_EMAILS', config['notifiers']['gmail']['required']['destination_emails']) # Set up logger LOGGER = logging.getLogger(__name__) LOGGER.setLevel(config['settings']['loglevel']) LOG_FORMAT = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') LOG_HANDLE = logging.StreamHandler() LOG_HANDLE.setLevel(logging.DEBUG) LOG_HANDLE.setFormatter(LOG_FORMAT) LOGGER.addHandler(LOG_HANDLE) exchange_interface = ExchangeInterface(config) strategy_analyzer = StrategyAnalyzer(config) notifier = Notifier(config) # The coin pairs coin_pairs = [] if config['settings']['market_pairs']: coin_pairs = config['settings']['market_pairs'].translate( str.maketrans('', '', whitespace)).split(",") else: user_markets = exchange_interface.get_user_markets() for user_market in user_markets['info']: if 'BTC' in user_market['Currency']: continue market_pair = user_market['Currency'] + '/BTC' coin_pairs.append(market_pair) LOGGER.debug(coin_pairs) while True: get_signal(coin_pairs, strategy_analyzer, notifier)
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(exchange_interface=None) 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 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)) 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 current_price = self.prices[i][4] current_rsi = rsi[i]["values"] if rsi[i] else None current_nine_period = nine_period[i]["values"] if nine_period[ i] else None current_fifteen_period = fifteen_period[i][ "values"] if fifteen_period[i] else None current_nine_period_ema = nine_period_ema[i][ "values"] 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 }) 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 Behaviour(): """Default analyzer which gives users basic trading information. """ def __init__(self, config, exchange_interface, notifier): """Initializes DefaultBehaviour class. Args: indicator_conf (dict): A dictionary of configuration for this analyzer. exchange_interface (ExchangeInterface): Instance of the ExchangeInterface class for making exchange queries. notifier (Notifier): Instance of the notifier class for informing a user when a threshold has been crossed. """ self.logger = structlog.get_logger() self.indicator_conf = config.indicators self.informant_conf = config.informants self.crossover_conf = config.crossovers self.exchange_interface = exchange_interface self.strategy_analyzer = StrategyAnalyzer() self.notifier = notifier output_interface = Output() self.output = output_interface.dispatcher def run(self, market_pairs, output_mode): """The analyzer entrypoint Args: market_pairs (list): List of symbol pairs to operate on, if empty get all pairs. output_mode (str): Which console output mode to use. """ self.logger.info("Starting default analyzer...") # if market_pairs: # self.logger.info("Found configured markets: %s", market_pairs) # else: # self.logger.info("No configured markets, using all available on exchange.") market_data = self.exchange_interface.get_exchange_markets( markets=market_pairs) self.logger.info("Using the following exchange(s): %s", list(market_data.keys())) new_result = self._test_strategies(market_data, output_mode) # self.notifier.notify_all(new_result) def _test_strategies(self, market_data, output_mode): """Test the strategies and perform notifications as required Args: market_data (dict): A dictionary containing the market data of the symbols to analyze. output_mode (str): Which console output mode to use. """ new_result = dict() for exchange in market_data: self.logger.info("Beginning analysis of %s", exchange) if exchange not in new_result: new_result[exchange] = dict() for market_pair in market_data[exchange]: My_new_result = dict() My_new_result[exchange] = dict() self.logger.info("Beginning analysis of %s", market_pair) if market_pair not in new_result[exchange]: new_result[exchange][market_pair] = dict() My_new_result[exchange][market_pair] = dict() new_result[exchange][market_pair][ 'indicators'] = self._get_indicator_results( exchange, market_pair) new_result[exchange][market_pair][ 'informants'] = self._get_informant_results( exchange, market_pair) new_result[exchange][market_pair][ 'crossovers'] = self._get_crossover_results( new_result[exchange][market_pair]) if output_mode in self.output: output_data = deepcopy(new_result[exchange][market_pair]) # print( # self.output[output_mode](output_data, market_pair), # end='' # ) else: self.logger.warn() My_new_result[exchange][market_pair] = new_result[exchange][ market_pair] self.notifier.notify_all_new(My_new_result) # Print an empty line when complete print() return new_result def _get_indicator_results(self, exchange, market_pair): """Execute the indicator analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ indicator_dispatcher = self.strategy_analyzer.indicator_dispatcher() results = { indicator: list() for indicator in self.indicator_conf.keys() } historical_data_cache = dict() for indicator in self.indicator_conf: if indicator not in indicator_dispatcher: self.logger.warn("No such indicator %s, skipping.", indicator) continue for indicator_conf in self.indicator_conf[indicator]: if indicator_conf['enabled']: candle_period = indicator_conf['candle_period'] else: self.logger.debug("%s is disabled, skipping.", indicator) continue if candle_period not in historical_data_cache: historical_data_cache[ candle_period] = self._get_historical_data( market_pair, exchange, candle_period) if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period], 'signal': indicator_conf['signal'], 'hot_thresh': indicator_conf['hot'], 'cold_thresh': indicator_conf['cold'] } if 'period_count' in indicator_conf: analysis_args['period_count'] = indicator_conf[ 'period_count'] results[indicator].append({ 'result': self._get_analysis_result(indicator_dispatcher, indicator, analysis_args, market_pair), 'config': indicator_conf }) return results def _get_informant_results(self, exchange, market_pair): """Execute the informant analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ informant_dispatcher = self.strategy_analyzer.informant_dispatcher() results = { informant: list() for informant in self.informant_conf.keys() } historical_data_cache = dict() for informant in self.informant_conf: if informant not in informant_dispatcher: self.logger.warn("No such informant %s, skipping.", informant) continue for informant_conf in self.informant_conf[informant]: if informant_conf['enabled']: candle_period = informant_conf['candle_period'] else: self.logger.debug("%s is disabled, skipping.", informant) continue if candle_period not in historical_data_cache: historical_data_cache[ candle_period] = self._get_historical_data( market_pair, exchange, candle_period) if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period] } if 'period_count' in informant_conf: analysis_args['period_count'] = informant_conf[ 'period_count'] results[informant].append({ 'result': self._get_analysis_result(informant_dispatcher, informant, analysis_args, market_pair), 'config': informant_conf }) return results def _get_crossover_results(self, new_result): """Execute crossover analysis on the results so far. Args: new_result (dict): A dictionary containing the results of the informant and indicator analysis. Returns: list: A list of dictinaries containing the results of the analysis. """ crossover_dispatcher = self.strategy_analyzer.crossover_dispatcher() results = { crossover: list() for crossover in self.crossover_conf.keys() } for crossover in self.crossover_conf: if crossover not in crossover_dispatcher: self.logger.warn("No such crossover %s, skipping.", crossover) continue for crossover_conf in self.crossover_conf[crossover]: if not crossover_conf['enabled']: self.logger.debug("%s is disabled, skipping.", crossover) continue try: key_indicator = new_result[ crossover_conf['key_indicator_type']][ crossover_conf['key_indicator']][ crossover_conf['key_indicator_index']] crossed_indicator = \ new_result[crossover_conf['crossed_indicator_type']][crossover_conf['crossed_indicator']][ crossover_conf['crossed_indicator_index']] except: self.logger.error( "IndexError: list index out of range, %s", crossover) continue dispatcher_args = { 'key_indicator': key_indicator['result'], 'key_signal': crossover_conf['key_signal'], 'key_indicator_index': crossover_conf['key_indicator_index'], 'crossed_indicator': crossed_indicator['result'], 'crossed_signal': crossover_conf['crossed_signal'], 'crossed_indicator_index': crossover_conf['crossed_indicator_index'] } results[crossover].append({ 'result': crossover_dispatcher[crossover](**dispatcher_args), 'config': crossover_conf }) return results def _get_historical_data(self, market_pair, exchange, candle_period): """Gets a list of OHLCV data for the given pair and exchange. Args: market_pair (str): The market pair to get the OHLCV data for. exchange (str): The exchange to get the OHLCV data for. candle_period (str): The timeperiod to collect for the given pair and exchange. Returns: list: A list of OHLCV data. """ historical_data = list() try: historical_data = self.exchange_interface.get_historical_data( market_pair, exchange, candle_period) except RetryError: self.logger.error( 'Too many retries fetching information for pair %s, skipping', market_pair) except ExchangeError: self.logger.error( 'Exchange supplied bad data for pair %s, skipping', market_pair) except ValueError as e: self.logger.error(e) self.logger.error( 'Invalid data encountered while processing pair %s, skipping', market_pair) self.logger.debug(traceback.format_exc()) except AttributeError: self.logger.error( 'Something went wrong fetching data for %s, skipping', market_pair) self.logger.debug(traceback.format_exc()) return historical_data def _get_analysis_result(self, dispatcher, indicator, dispatcher_args, market_pair): """Get the results of performing technical analysis Args: dispatcher (dict): A dictionary of functions for performing TA. indicator (str): The name of the desired indicator. dispatcher_args (dict): A dictionary of arguments to provide the analyser market_pair (str): The market pair to analyse Returns: pandas.DataFrame: Returns a pandas.DataFrame of results or an empty string. """ try: results = dispatcher[indicator](**dispatcher_args) except TypeError: self.logger.info( 'Invalid type encountered while processing pair %s for indicator %s, skipping', market_pair, indicator) self.logger.info(traceback.format_exc()) results = str() return results
class Behaviour(): """Default analyzer which gives users basic trading information. """ def __init__(self, config, exchange_interface, notifier): """Initializes DefaultBehaviour class. Args: indicator_conf (dict): A dictionary of configuration for this analyzer. exchange_interface (ExchangeInterface): Instance of the ExchangeInterface class for making exchange queries. notifier (Notifier): Instance of the notifier class for informing a user when a threshold has been crossed. """ self.logger = structlog.get_logger() self.indicator_conf = config.indicators self.informant_conf = config.informants self.crossover_conf = config.crossovers self.exchange_interface = exchange_interface self.strategy_analyzer = StrategyAnalyzer() self.notifier = notifier output_interface = Output() self.output = output_interface.dispatcher def run(self, market_pairs, output_mode): """The analyzer entrypoint Args: market_pairs (list): List of symbol pairs to operate on, if empty get all pairs. output_mode (str): Which console output mode to use. """ self.logger.info("Starting default analyzer...") if market_pairs: self.logger.info("Found configured markets: %s", market_pairs) else: self.logger.info("No configured markets, using all available on exchange.") market_data = self.exchange_interface.get_exchange_markets(markets=market_pairs) self.logger.info("Using the following exchange(s): %s", list(market_data.keys())) new_result = self._test_strategies(market_data, output_mode) self.notifier.notify_all(new_result) def _test_strategies(self, market_data, output_mode): """Test the strategies and perform notifications as required Args: market_data (dict): A dictionary containing the market data of the symbols to analyze. output_mode (str): Which console output mode to use. """ new_result = dict() for exchange in market_data: self.logger.info("Beginning analysis of %s", exchange) if exchange not in new_result: new_result[exchange] = dict() for market_pair in market_data[exchange]: self.logger.info("Beginning analysis of %s", market_pair) if market_pair not in new_result[exchange]: new_result[exchange][market_pair] = dict() new_result[exchange][market_pair]['indicators'] = self._get_indicator_results( exchange, market_pair ) new_result[exchange][market_pair]['informants'] = self._get_informant_results( exchange, market_pair ) new_result[exchange][market_pair]['crossovers'] = self._get_crossover_results( new_result[exchange][market_pair] ) if output_mode in self.output: output_data = deepcopy(new_result[exchange][market_pair]) print( self.output[output_mode](output_data, market_pair), end='' ) else: self.logger.warn() # Print an empty line when complete print() return new_result def _get_indicator_results(self, exchange, market_pair): """Execute the indicator analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ indicator_dispatcher = self.strategy_analyzer.indicator_dispatcher() results = { indicator: list() for indicator in self.indicator_conf.keys() } historical_data_cache = dict() for indicator in self.indicator_conf: if indicator not in indicator_dispatcher: self.logger.warn("No such indicator %s, skipping.", indicator) continue for indicator_conf in self.indicator_conf[indicator]: if indicator_conf['enabled']: candle_period = indicator_conf['candle_period'] else: self.logger.debug("%s is disabled, skipping.", indicator) continue if candle_period not in historical_data_cache: historical_data_cache[candle_period] = self._get_historical_data( market_pair, exchange, candle_period ) if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period], 'signal': indicator_conf['signal'], 'hot_thresh': indicator_conf['hot'], 'cold_thresh': indicator_conf['cold'] } if 'period_count' in indicator_conf: analysis_args['period_count'] = indicator_conf['period_count'] results[indicator].append({ 'result': self._get_analysis_result( indicator_dispatcher, indicator, analysis_args, market_pair ), 'config': indicator_conf }) return results def _get_informant_results(self, exchange, market_pair): """Execute the informant analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ informant_dispatcher = self.strategy_analyzer.informant_dispatcher() results = { informant: list() for informant in self.informant_conf.keys() } historical_data_cache = dict() for informant in self.informant_conf: if informant not in informant_dispatcher: self.logger.warn("No such informant %s, skipping.", informant) continue for informant_conf in self.informant_conf[informant]: if informant_conf['enabled']: candle_period = informant_conf['candle_period'] else: self.logger.debug("%s is disabled, skipping.", informant) continue if candle_period not in historical_data_cache: historical_data_cache[candle_period] = self._get_historical_data( market_pair, exchange, candle_period ) if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period] } if 'period_count' in informant_conf: analysis_args['period_count'] = informant_conf['period_count'] results[informant].append({ 'result': self._get_analysis_result( informant_dispatcher, informant, analysis_args, market_pair ), 'config': informant_conf }) return results def _get_crossover_results(self, new_result): """Execute crossover analysis on the results so far. Args: new_result (dict): A dictionary containing the results of the informant and indicator analysis. Returns: list: A list of dictinaries containing the results of the analysis. """ crossover_dispatcher = self.strategy_analyzer.crossover_dispatcher() results = { crossover: list() for crossover in self.crossover_conf.keys() } for crossover in self.crossover_conf: if crossover not in crossover_dispatcher: self.logger.warn("No such crossover %s, skipping.", crossover) continue for crossover_conf in self.crossover_conf[crossover]: if not crossover_conf['enabled']: self.logger.debug("%s is disabled, skipping.", crossover) continue key_indicator = new_result[crossover_conf['key_indicator_type']][crossover_conf['key_indicator']][crossover_conf['key_indicator_index']] crossed_indicator = new_result[crossover_conf['crossed_indicator_type']][crossover_conf['crossed_indicator']][crossover_conf['crossed_indicator_index']] dispatcher_args = { 'key_indicator': key_indicator['result'], 'key_signal': crossover_conf['key_signal'], 'key_indicator_index': crossover_conf['key_indicator_index'], 'crossed_indicator': crossed_indicator['result'], 'crossed_signal': crossover_conf['crossed_signal'], 'crossed_indicator_index': crossover_conf['crossed_indicator_index'] } results[crossover].append({ 'result': crossover_dispatcher[crossover](**dispatcher_args), 'config': crossover_conf }) return results def _get_historical_data(self, market_pair, exchange, candle_period): """Gets a list of OHLCV data for the given pair and exchange. Args: market_pair (str): The market pair to get the OHLCV data for. exchange (str): The exchange to get the OHLCV data for. candle_period (str): The timeperiod to collect for the given pair and exchange. Returns: list: A list of OHLCV data. """ historical_data = list() try: historical_data = self.exchange_interface.get_historical_data( market_pair, exchange, candle_period ) except RetryError: self.logger.error( 'Too many retries fetching information for pair %s, skipping', market_pair ) except ExchangeError: self.logger.error( 'Exchange supplied bad data for pair %s, skipping', market_pair ) except ValueError as e: self.logger.error(e) self.logger.error( 'Invalid data encountered while processing pair %s, skipping', market_pair ) self.logger.debug(traceback.format_exc()) except AttributeError: self.logger.error( 'Something went wrong fetching data for %s, skipping', market_pair ) self.logger.debug(traceback.format_exc()) return historical_data def _get_analysis_result(self, dispatcher, indicator, dispatcher_args, market_pair): """Get the results of performing technical analysis Args: dispatcher (dict): A dictionary of functions for performing TA. indicator (str): The name of the desired indicator. dispatcher_args (dict): A dictionary of arguments to provide the analyser market_pair (str): The market pair to analyse Returns: pandas.DataFrame: Returns a pandas.DataFrame of results or an empty string. """ try: results = dispatcher[indicator](**dispatcher_args) except TypeError: self.logger.info( 'Invalid type encountered while processing pair %s for indicator %s, skipping', market_pair, indicator ) self.logger.info(traceback.format_exc()) results = str() return results
class Behaviour(): """Default analyzer which gives users basic trading information. """ def __init__(self, config, exchange_interface, notifier): """Initializes DefaultBehaviour class. Args: indicator_conf (dict): A dictionary of configuration for this analyzer. exchange_interface (ExchangeInterface): Instance of the ExchangeInterface class for making exchange queries. notifier (Notifier): Instance of the notifier class for informing a user when a threshold has been crossed. """ self.logger = structlog.get_logger() self.indicator_conf = config.indicators self.informant_conf = config.informants self.crossover_conf = config.crossovers self.exchange_interface = exchange_interface self.strategy_analyzer = StrategyAnalyzer() self.notifier = notifier self.all_historical_data = dict() output_interface = Output() self.output = output_interface.dispatcher self.enable_charts = config.settings['enable_charts'] self.timezone = config.settings['timezone'] def run(self, market_data, output_mode): """The analyzer entrypoint Args: market_data (dict): Dict of exchanges and symbol pairs to operate on. output_mode (str): Which console output mode to use. """ self.logger.info("Starting default analyzer...") self.logger.info("Using the following exchange(s): %s", list(market_data.keys())) self.all_historical_data = self.get_all_historical_data(market_data) new_result = self._test_strategies(market_data, output_mode) self.notifier.set_timezone(self.timezone) if self.enable_charts: self.notifier.set_enable_charts(True) self.notifier.set_all_historical_data(self.all_historical_data) self.notifier.notify_all(new_result) def get_all_historical_data(self, market_data): """Get historical data for each exchange/market pair/candle period Args: market_data (dict): A dictionary containing the market data of the symbols to get data. """ indicator_dispatcher = self.strategy_analyzer.indicator_dispatcher() informant_dispatcher = self.strategy_analyzer.informant_dispatcher() data = dict() for exchange in market_data: self.logger.info("Getting data for %s", list(market_data[exchange].keys())) if exchange not in data: data[exchange] = dict() for market_pair in market_data[exchange]: if market_pair not in data[exchange]: data[exchange][market_pair] = dict() for indicator in self.indicator_conf: if indicator not in indicator_dispatcher: self.logger.warn("No such indicator %s, skipping.", indicator) continue for indicator_conf in self.indicator_conf[indicator]: if indicator_conf['enabled']: candle_period = indicator_conf['candle_period'] if candle_period not in data[exchange][ market_pair]: data[exchange][market_pair][ candle_period] = self._get_historical_data( market_pair, exchange, candle_period) for informant in self.informant_conf: if informant not in informant_dispatcher: self.logger.warn("No such informant %s, skipping.", informant) continue for informant_conf in self.informant_conf[informant]: if informant_conf['enabled']: candle_period = informant_conf['candle_period'] if candle_period not in data[exchange][ market_pair]: data[exchange][market_pair][ candle_period] = self._get_historical_data( market_pair, exchange, candle_period) return data def _test_strategies(self, market_data, output_mode): """Test the strategies and perform notifications as required Args: market_data (dict): A dictionary containing the market data of the symbols to analyze. output_mode (str): Which console output mode to use. """ new_result = dict() for exchange in market_data: self.logger.info("Beginning analysis of %s", exchange) if exchange not in new_result: new_result[exchange] = dict() for market_pair in market_data[exchange]: self.logger.info("Beginning analysis of %s", market_pair) if market_pair not in new_result[exchange]: new_result[exchange][market_pair] = dict() new_result[exchange][market_pair][ 'indicators'] = self._get_indicator_results( exchange, market_pair) new_result[exchange][market_pair][ 'informants'] = self._get_informant_results( exchange, market_pair) new_result[exchange][market_pair][ 'crossovers'] = self._get_crossover_results( new_result[exchange][market_pair]) if output_mode in self.output: output_data = deepcopy(new_result[exchange][market_pair]) print(self.output[output_mode](output_data, market_pair), end='') else: self.logger.warn() # Print an empty line when complete print() return new_result def _get_indicator_results(self, exchange, market_pair): """Execute the indicator analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ indicator_dispatcher = self.strategy_analyzer.indicator_dispatcher() results = { indicator: list() for indicator in self.indicator_conf.keys() } historical_data_cache = self.all_historical_data[exchange][market_pair] for indicator in self.indicator_conf: if indicator not in indicator_dispatcher: self.logger.warn("No such indicator %s, skipping.", indicator) continue for indicator_conf in self.indicator_conf[indicator]: if not indicator_conf['enabled']: continue candle_period = indicator_conf['candle_period'] #Exchange doesnt support such candle period if candle_period not in historical_data_cache: continue if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period], 'signal': indicator_conf['signal'], 'hot_thresh': indicator_conf['hot'] if 'hot' in indicator_conf else 0, 'cold_thresh': indicator_conf['cold'] if 'cold' in indicator_conf else 0 } if 'period_count' in indicator_conf: analysis_args['period_count'] = indicator_conf[ 'period_count'] if indicator == 'rsi' and 'lrsi_filter' in indicator_conf: analysis_args['lrsi_filter'] = indicator_conf[ 'lrsi_filter'] if indicator == 'ma_ribbon': analysis_args['pval_th'] = indicator_conf[ 'pval_th'] if 'pval_th' in indicator_conf else 20 if 'ma_series' in indicator_conf: series = indicator_conf['ma_series'] analysis_args['ma_series'] = [ int(i) for i in series.replace(' ', '').split(',') ] else: analysis_args['ma_series'] = [5, 15, 25, 35, 45] if indicator == 'ma_crossover': analysis_args['exponential'] = indicator_conf[ 'exponential'] if 'exponential' in indicator_conf else False analysis_args['ma_fast'] = indicator_conf[ 'ma_fast'] if 'ma_fast' in indicator_conf else 13 analysis_args['ma_slow'] = indicator_conf[ 'ma_slow'] if 'ma_slow' in indicator_conf else 30 results[indicator].append({ 'result': self._get_analysis_result(indicator_dispatcher, indicator, analysis_args, market_pair), 'config': indicator_conf }) return results def _get_informant_results(self, exchange, market_pair): """Execute the informant analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ informant_dispatcher = self.strategy_analyzer.informant_dispatcher() results = { informant: list() for informant in self.informant_conf.keys() } historical_data_cache = self.all_historical_data[exchange][market_pair] for informant in self.informant_conf: if informant not in informant_dispatcher: self.logger.warn("No such informant %s, skipping.", informant) continue for informant_conf in self.informant_conf[informant]: if not informant_conf['enabled']: continue candle_period = informant_conf['candle_period'] #Exchange doesnt support such candle period if candle_period not in historical_data_cache: continue if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period] } if 'period_count' in informant_conf: analysis_args['period_count'] = informant_conf[ 'period_count'] results[informant].append({ 'result': self._get_analysis_result(informant_dispatcher, informant, analysis_args, market_pair), 'config': informant_conf }) return results def _get_crossover_results(self, new_result): """Execute crossover analysis on the results so far. Args: new_result (dict): A dictionary containing the results of the informant and indicator analysis. Returns: list: A list of dictinaries containing the results of the analysis. """ crossover_dispatcher = self.strategy_analyzer.crossover_dispatcher() results = { crossover: list() for crossover in self.crossover_conf.keys() } for crossover in self.crossover_conf: if crossover not in crossover_dispatcher: self.logger.warn("No such crossover %s, skipping.", crossover) continue for crossover_conf in self.crossover_conf[crossover]: if not crossover_conf['enabled']: self.logger.debug("%s is disabled, skipping.", crossover) continue key_indicator = new_result[crossover_conf[ 'key_indicator_type']][crossover_conf['key_indicator']][ crossover_conf['key_indicator_index']] crossed_indicator = new_result[ crossover_conf['crossed_indicator_type']][ crossover_conf['crossed_indicator']][ crossover_conf['crossed_indicator_index']] crossover_conf[ 'candle_period'] = crossover_conf['key_indicator'] + str( crossover_conf['key_indicator_index']) dispatcher_args = { 'key_indicator': key_indicator['result'], 'key_signal': crossover_conf['key_signal'], 'key_indicator_index': crossover_conf['key_indicator_index'], 'crossed_indicator': crossed_indicator['result'], 'crossed_signal': crossover_conf['crossed_signal'], 'crossed_indicator_index': crossover_conf['crossed_indicator_index'] } results[crossover].append({ 'result': crossover_dispatcher[crossover](**dispatcher_args), 'config': crossover_conf }) return results def _get_historical_data(self, market_pair, exchange, candle_period): """Gets a list of OHLCV data for the given pair and exchange. Args: market_pair (str): The market pair to get the OHLCV data for. exchange (str): The exchange to get the OHLCV data for. candle_period (str): The timeperiod to collect for the given pair and exchange. Returns: list: A list of OHLCV data. """ historical_data = list() try: historical_data = self.exchange_interface.get_historical_data( market_pair, exchange, candle_period) except RetryError: self.logger.error( 'Too many retries fetching information for pair %s, skipping', market_pair) except ExchangeError: self.logger.error( 'Exchange supplied bad data for pair %s, skipping', market_pair) except ValueError as e: self.logger.error(e) self.logger.error( 'Invalid data encountered while processing pair %s, skipping', market_pair) self.logger.debug(traceback.format_exc()) except AttributeError: self.logger.error( 'Something went wrong fetching data for %s, skipping', market_pair) self.logger.debug(traceback.format_exc()) return historical_data def _get_analysis_result(self, dispatcher, indicator, dispatcher_args, market_pair): """Get the results of performing technical analysis Args: dispatcher (dict): A dictionary of functions for performing TA. indicator (str): The name of the desired indicator. dispatcher_args (dict): A dictionary of arguments to provide the analyser market_pair (str): The market pair to analyse Returns: pandas.DataFrame: Returns a pandas.DataFrame of results or an empty string. """ try: results = dispatcher[indicator](**dispatcher_args) except TypeError: self.logger.info( 'Invalid type encountered while processing pair %s for indicator %s, skipping', market_pair, indicator) self.logger.info(traceback.format_exc()) results = str() return results
class Chart(object): def __init__(self, pair, period, exchange_interface): self.pair = pair self.indicators = StrategyAnalyzer() self.data = [] # Query the data to fill our chart truncate it to 'length' elements raw_data = exchange_interface.get_historical_data( pair, interval=Chart.period_to_integer(period)) for datum in raw_data: stick = Candlestick(time=datum[0], open=datum[1], high=datum[2], low=datum[3], close=datum[4], price_average=(datum[2] + datum[3]) / 2.) self.data.append(stick) def get_points(self, start_time=None): """ Retrieve all candlesticks from the chart. If a start time is specified, then we return candlesticks only from that time onward. By default we return all candlesticks Args: start_time (int): Defaults to None. Otherwise is an integer denoting the starting time in epoch seconds that our candles will be returned from Returns: list[Candlestick]: A list of candlestick objects """ if start_time: return [stick for stick in self.data if stick.time >= start_time] return self.data @staticmethod def period_to_integer(period): """ Converts a period string into a integer Args: period (str): A string denoting the period of time each candlestick represents (i.e., '15m') Returns: int: An integer denoting the period of time in seconds """ import re try: num_units = re.findall(r'\d+', period)[0] unit_type = period[len(num_units):] if unit_type == 'm': return 60 * int(num_units) if unit_type == 'h': return 60 * 60 * int(num_units) if unit_type == 'd': return 24 * 60 * 60 * int(num_units) except IndexError: raise ValueError( "`Period` string should contain a character prefixed with an integer" ) def get_indicators(self, **kwargs): ''' Returns the indicators specified in the **kwargs dictionary as a json-serializable dictionary ''' from math import isnan # Indicators are hardcoded for now. Will be updated to accommodate variable-sized MA's response = { 'bollinger_upper': [], 'bollinger_lower': [], 'sma9': [], 'sma15': [] } # 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)) ] return response
class Behaviour(IndicatorUtils): """Default analyzer which gives users basic trading information. """ def __init__(self, config, exchange_interface): """Initializes DefaultBehaviour class. Args: indicator_conf (dict): A dictionary of configuration for this analyzer. exchange_interface (ExchangeInterface): Instance of the ExchangeInterface class for making exchange queries. """ self.logger = structlog.get_logger() self.indicator_conf = config.indicators self.informant_conf = config.informants self.crossover_conf = config.crossovers self.notifiers_conf = config.notifiers self.exchange_interface = exchange_interface self.strategy_analyzer = StrategyAnalyzer() self.all_historical_data = dict() self.last_analysis = dict() self.timezone = config.settings['timezone'] output_interface = Output() self.output = output_interface.dispatcher def run(self, exchange, market_data, fibonacci, output_mode): """The analyzer entrypoint Args: market_data (dict): Dict of exchanges and symbol pairs to operate on. fibonacci (dict): Dict with Fibonacci levels output_mode (str): Which console output mode to use. """ self.logger.info("Starting default analyzer for %s ...", exchange) self.all_historical_data = self.get_all_historical_data(market_data) new_analysis = self._test_strategies(market_data, output_mode) template = self.notifiers_conf['telegram']['optional']['template'] indicator_messages = self.get_indicator_messages( new_analysis, market_data, template) self._create_charts(exchange, indicator_messages, fibonacci) return indicator_messages def get_all_historical_data(self, market_data): """Get historical data for each exchange/market pair/candle period Args: market_data (dict): A dictionary containing the market data of the symbols to get data. """ indicator_dispatcher = self.strategy_analyzer.indicator_dispatcher() data = dict() for exchange in market_data: self.logger.info("Getting candle data of %s", exchange) if exchange not in data: data[exchange] = dict() for market_pair in market_data[exchange]: if market_pair not in data[exchange]: data[exchange][market_pair] = dict() for indicator in self.indicator_conf: if indicator not in indicator_dispatcher: self.logger.warn("No such indicator %s, skipping.", indicator) continue for indicator_conf in self.indicator_conf[indicator]: if indicator_conf['enabled']: candle_period = indicator_conf['candle_period'] if candle_period not in data[exchange][ market_pair]: candle_data = self._get_historical_data( market_pair, exchange, candle_period) if len(candle_data) == 0: self.logger.warn( 'No candle data for %s %s on %s', market_pair, candle_period, exchange) continue data[exchange][market_pair][ candle_period] = candle_data #Return after iterate all exchanges return data def _test_strategies(self, market_data, output_mode): """Test the strategies and perform notifications as required Args: market_data (dict): A dictionary containing the market data of the symbols to analyze. output_mode (str): Which console output mode to use. """ new_result = dict() for exchange in market_data: if exchange not in new_result: new_result[exchange] = dict() for market_pair in market_data[exchange]: self.logger.info("Beginning analysis of %s on %s" % (market_pair, exchange)) if market_pair not in new_result[exchange]: new_result[exchange][market_pair] = dict() new_result[exchange][market_pair][ 'indicators'] = self._get_indicator_results( exchange, market_pair) new_result[exchange][market_pair][ 'informants'] = self._get_informant_results( exchange, market_pair) new_result[exchange][market_pair][ 'crossovers'] = self._get_crossover_results( new_result[exchange][market_pair]) if output_mode in self.output: output_data = deepcopy(new_result[exchange][market_pair]) print(self.output[output_mode](output_data, market_pair), end='') else: self.logger.warn() # Print an empty line when complete print() return new_result def _get_indicator_results(self, exchange, market_pair): """Execute the indicator analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ indicator_dispatcher = self.strategy_analyzer.indicator_dispatcher() results = { indicator: list() for indicator in self.indicator_conf.keys() } historical_data_cache = self.all_historical_data[exchange][market_pair] for indicator in self.indicator_conf: if indicator not in indicator_dispatcher: self.logger.warn("No such indicator %s, skipping.", indicator) continue for indicator_conf in self.indicator_conf[indicator]: if not indicator_conf['enabled']: continue candle_period = indicator_conf['candle_period'] #Exchange doesnt support such candle period if candle_period not in historical_data_cache: continue if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period], 'signal': indicator_conf['signal'], 'hot_thresh': indicator_conf['hot'], 'cold_thresh': indicator_conf['cold'] } if 'period_count' in indicator_conf: analysis_args['period_count'] = indicator_conf[ 'period_count'] if indicator == 'rsi' and 'lrsi_filter' in indicator_conf: analysis_args['lrsi_filter'] = indicator_conf[ 'lrsi_filter'] results[indicator].append({ 'result': self._get_analysis_result(indicator_dispatcher, indicator, analysis_args, market_pair), 'config': indicator_conf }) return results def _get_informant_results(self, exchange, market_pair): """Execute the informant analysis on a particular exchange and pair. Args: exchange (str): The exchange to get the indicator results for. market_pair (str): The pair to get the market pair results for. Returns: list: A list of dictinaries containing the results of the analysis. """ informant_dispatcher = self.strategy_analyzer.informant_dispatcher() results = { informant: list() for informant in self.informant_conf.keys() } #historical_data_cache = dict() historical_data_cache = self.all_historical_data[exchange][market_pair] for informant in self.informant_conf: if informant not in informant_dispatcher: self.logger.warn("No such informant %s, skipping.", informant) continue for informant_conf in self.informant_conf[informant]: if not informant_conf['enabled']: continue candle_period = informant_conf['candle_period'] #Exchange doesnt support such candle period if candle_period not in historical_data_cache: continue if historical_data_cache[candle_period]: analysis_args = { 'historical_data': historical_data_cache[candle_period] } if 'period_count' in informant_conf: analysis_args['period_count'] = informant_conf[ 'period_count'] results[informant].append({ 'result': self._get_analysis_result(informant_dispatcher, informant, analysis_args, market_pair), 'config': informant_conf }) return results def _get_crossover_results(self, new_result): """Execute crossover analysis on the results so far. Args: new_result (dict): A dictionary containing the results of the informant and indicator analysis. Returns: list: A list of dictinaries containing the results of the analysis. """ crossover_dispatcher = self.strategy_analyzer.crossover_dispatcher() results = { crossover: list() for crossover in self.crossover_conf.keys() } for crossover in self.crossover_conf: if crossover not in crossover_dispatcher: self.logger.warn("No such crossover %s, skipping.", crossover) continue for crossover_conf in self.crossover_conf[crossover]: if not crossover_conf['enabled']: self.logger.debug("%s is disabled, skipping.", crossover) continue key_indicator = new_result[crossover_conf[ 'key_indicator_type']][crossover_conf['key_indicator']][ crossover_conf['key_indicator_index']] crossed_indicator = new_result[ crossover_conf['crossed_indicator_type']][ crossover_conf['crossed_indicator']][ crossover_conf['crossed_indicator_index']] dispatcher_args = { 'key_indicator': key_indicator['result'], 'key_signal': crossover_conf['key_signal'], 'key_indicator_index': crossover_conf['key_indicator_index'], 'crossed_indicator': crossed_indicator['result'], 'crossed_signal': crossover_conf['crossed_signal'], 'crossed_indicator_index': crossover_conf['crossed_indicator_index'] } results[crossover].append({ 'result': crossover_dispatcher[crossover](**dispatcher_args), 'config': crossover_conf }) return results def _get_historical_data(self, market_pair, exchange, candle_period): """Gets a list of OHLCV data for the given pair and exchange. Args: market_pair (str): The market pair to get the OHLCV data for. exchange (str): The exchange to get the OHLCV data for. candle_period (str): The timeperiod to collect for the given pair and exchange. Returns: list: A list of OHLCV data. """ historical_data = list() try: historical_data = self.exchange_interface.get_historical_data( market_pair, exchange, candle_period) except RetryError: self.logger.error( 'Too many retries fetching information for pair %s, skipping', market_pair) except ExchangeError: self.logger.error( 'Exchange supplied bad data for pair %s, skipping', market_pair) except ValueError as e: self.logger.error(e) self.logger.error( 'Invalid data encountered while processing pair %s, skipping', market_pair) self.logger.debug(traceback.format_exc()) except AttributeError: self.logger.error( 'Something went wrong fetching data for %s, skipping', market_pair) self.logger.debug(traceback.format_exc()) return historical_data def _get_analysis_result(self, dispatcher, indicator, dispatcher_args, market_pair): """Get the results of performing technical analysis Args: dispatcher (dict): A dictionary of functions for performing TA. indicator (str): The name of the desired indicator. dispatcher_args (dict): A dictionary of arguments to provide the analyser market_pair (str): The market pair to analyse Returns: pandas.DataFrame: Returns a pandas.DataFrame of results or an empty string. """ try: results = dispatcher[indicator](**dispatcher_args) except TypeError: self.logger.info( 'Invalid type encountered while processing pair %s for indicator %s, skipping', market_pair, indicator) self.logger.info(traceback.format_exc()) results = str() return results def _create_charts(self, exchange, indicator_messages, fibonacci): """Create charts for each market_pair/candle_period Args: market_data (dict): A dictionary containing the market data of the symbols """ charts_dir = './charts' now = datetime.now(timezone(self.timezone)) creation_date = now.strftime("%Y-%m-%d %H:%M:%S") if not os.path.exists(charts_dir): os.mkdir(charts_dir) for market_pair in indicator_messages[exchange]: candle_messages = indicator_messages[exchange][market_pair] #indicators = new_analysis[exchange][market_pair]['indicators'] #OBV indicators #obv = dict() #for index, analysis in enumerate(indicators['obv']): #if analysis['result'].shape[0] == 0: #continue #obv[analysis['config']['candle_period']] = analysis['result'] historical_data = self.all_historical_data[exchange][market_pair] fibonacci_levels = fibonacci[exchange][market_pair] for candle_period in candle_messages: if len(candle_messages[candle_period]) == 0: continue candles_data = historical_data[candle_period] self.logger.info('Creating chart for %s %s %s', exchange, market_pair, candle_period) self._create_chart(exchange, market_pair, candle_period, candles_data, fibonacci_levels, charts_dir, creation_date) def _create_chart(self, exchange, market_pair, candle_period, candles_data, fibonacci_levels, charts_dir, creation_date): df = self.convert_to_dataframe(candles_data) plt.rc('axes', grid=True) plt.rc('grid', color='#b0b0b0', linestyle='-', linewidth=0.2, alpha=0.6) left, width = 0.1, 0.8 rect1 = [left, 0.7, width, 0.25] rect2 = [left, 0.5, width, 0.2] rect3 = [left, 0.3, width, 0.2] rect4 = [left, 0.1, width, 0.2] fig = plt.figure(facecolor='white') fig.set_size_inches(10, 16, forward=True) axescolor = '#f6f6f6' # the axes background color ax1 = fig.add_axes(rect1, facecolor=axescolor) # left, bottom, width, height ax2 = fig.add_axes(rect2, facecolor=axescolor, sharex=ax1) ax3 = fig.add_axes(rect3, facecolor=axescolor, sharex=ax1) ax4 = fig.add_axes(rect4, facecolor=axescolor, sharex=ax1) if fibonacci_levels['0.00'] > 0 and fibonacci_levels[ '0.00'] > fibonacci_levels['100.00']: ax1.axhline(fibonacci_levels['0.00'], color='steelblue', linestyle='-', alpha=0.3) ax1.axhspan(fibonacci_levels['23.60'], fibonacci_levels['0.00'], alpha=0.2, color='steelblue') ax1.axhspan(fibonacci_levels['38.20'], fibonacci_levels['23.60'], alpha=0.3, color='steelblue') ax1.axhspan(fibonacci_levels['50.00'], fibonacci_levels['38.20'], alpha=0.2, color='steelblue') ax1.axhspan(fibonacci_levels['61.80'], fibonacci_levels['50.00'], alpha=0.3, color='steelblue') ax1.axhspan(fibonacci_levels['78.60'], fibonacci_levels['61.80'], alpha=0.2, color='steelblue') ax1.axhspan(fibonacci_levels['100.00'], fibonacci_levels['78.60'], alpha=0.3, color='steelblue') #Plot Candles chart self.plot_candlestick(ax1, df, candle_period) #Plot RSI (14) self.plot_rsi(ax2, df) #Plot OBV indicator with data of last analysis #if candle_period in obv : self.plot_obv(ax3, candles_data, candle_period) # Calculate and plot MACD self.plot_macd(ax4, df, candle_period) for ax in ax1, ax2, ax3, ax4: if ax != ax4: for label in ax.get_xticklabels(): label.set_visible(False) else: for label in ax.get_xticklabels(): label.set_rotation(30) label.set_horizontalalignment('right') ax.xaxis.set_major_locator(mticker.MaxNLocator(10)) ax.xaxis.set_major_formatter(DateFormatter('%d/%b')) ax.xaxis.set_tick_params(which='major', pad=15) fig.autofmt_xdate() title = '{} {} {} - {}'.format(exchange, market_pair, candle_period, creation_date).upper() fig.suptitle(title, fontsize=14) market_pair = market_pair.replace('/', '_').lower() chart_file = '{}/{}_{}_{}.png'.format(charts_dir, exchange, market_pair, candle_period) plt.savefig(chart_file) plt.close(fig) def candlestick_ohlc(self, ax, quotes, width=0.2, colorup='k', colordown='r', alpha=1.0, ochl=False): """ Plot the time, open, high, low, close as a vertical line ranging from low to high. Use a rectangular bar to represent the open-close span. If close >= open, use colorup to color the bar, otherwise use colordown Parameters ---------- ax : `Axes` an Axes instance to plot to quotes : sequence of quote sequences data to plot. time must be in float date format - see date2num (time, open, high, low, close, ...) vs (time, open, close, high, low, ...) set by `ochl` width : float fraction of a day for the rectangle width colorup : color the color of the rectangle where close >= open colordown : color the color of the rectangle where close < open alpha : float the rectangle alpha level ochl: bool argument to select between ochl and ohlc ordering of quotes Returns ------- ret : tuple returns (lines, patches) where lines is a list of lines added and patches is a list of the rectangle patches added """ OFFSET = width / 2.0 lines = [] patches = [] for q in quotes: if ochl: t, open, close, high, low = q[:5] else: t, open, high, low, close = q[:5] if close >= open: color = colorup lower = open height = close - open else: color = colordown lower = close height = open - close vline = Line2D( xdata=(t, t), ydata=(low, high), color=color, linewidth=0.5, antialiased=False, ) rect = Rectangle(xy=(t - OFFSET, lower), width=width, height=height, facecolor=color, edgecolor=None, antialiased=False, alpha=1.0) lines.append(vline) patches.append(rect) ax.add_line(vline) ax.add_patch(rect) ax.autoscale_view() return lines, patches def plot_candlestick(self, ax, df, candle_period): textsize = 11 _time = mdates.date2num(df.index.to_pydatetime()) min_x = np.nanmin(_time) max_x = np.nanmax(_time) stick_width = ((max_x - min_x) / _time.size) prices = df["close"] ax.set_ymargin(0.2) ax.ticklabel_format(axis='y', style='plain') self.candlestick_ohlc(ax, zip(_time, df['open'], df['high'], df['low'], df['close']), width=stick_width, colorup='olivedrab', colordown='crimson') ma25 = self.moving_average(prices, 25, type='simple') ma7 = self.moving_average(prices, 7, type='simple') ax.plot(df.index, ma25, color='indigo', lw=0.6, label='MA (25)') ax.plot(df.index, ma7, color='orange', lw=0.6, label='MA (7)') ax.text(0.04, 0.94, 'MA (7, close, 0)', color='orange', transform=ax.transAxes, fontsize=textsize, va='top') ax.text(0.24, 0.94, 'MA (25, close, 0)', color='indigo', transform=ax.transAxes, fontsize=textsize, va='top') def plot_rsi(self, ax, df): textsize = 11 fillcolor = 'darkmagenta' rsi = self.relative_strength(df["close"]) ax.plot(df.index, rsi, color=fillcolor, linewidth=0.5) ax.axhline(70, color='darkmagenta', linestyle='dashed', alpha=0.6) ax.axhline(30, color='darkmagenta', linestyle='dashed', alpha=0.6) ax.fill_between(df.index, rsi, 70, where=(rsi >= 70), facecolor=fillcolor, edgecolor=fillcolor) ax.fill_between(df.index, rsi, 30, where=(rsi <= 30), facecolor=fillcolor, edgecolor=fillcolor) ax.set_ylim(0, 100) ax.set_yticks([30, 70]) ax.text(0.024, 0.94, 'RSI (14)', va='top', transform=ax.transAxes, fontsize=textsize) def plot_macd(self, ax, df, candle_period): textsize = 11 df = StockDataFrame.retype(df) df['macd'] = df.get('macd') min_y = df.macd.min() max_y = df.macd.max() #Reduce Macd Histogram values to have a better visualization macd_h = df.macdh * 0.5 if (macd_h.min() < min_y): min_y = macd_h.min() if (macd_h.max() > max_y): max_y = macd_h.max() #Adding some extra space at bottom/top min_y = min_y * 1.2 max_y = max_y * 1.2 #Define candle bar width _time = mdates.date2num(df.index.to_pydatetime()) min_x = np.nanmin(_time) max_x = np.nanmax(_time) bar_width = ((max_x - min_x) / _time.size) * 0.8 ax.bar(x=_time, bottom=[0 for _ in macd_h.index], height=macd_h, width=bar_width, color="red", alpha=0.4) ax.plot(_time, df.macd, color='blue', lw=0.6) ax.plot(_time, df.macds, color='red', lw=0.6) ax.set_ylim((min_y, max_y)) ax.yaxis.set_major_locator(mticker.MaxNLocator(nbins=5, prune='upper')) ax.text(0.024, 0.94, 'MACD (12, 26, close, 9)', va='top', transform=ax.transAxes, fontsize=textsize) def plot_obv(self, ax, candles_data, candle_period): obv_df = OBV().analyze(candles_data) textsize = 11 min_y = obv_df.obv.min() max_y = obv_df.obv.max() #Adding some extra space at bottom/top min_y = min_y * 1.2 max_y = max_y * 1.2 _time = mdates.date2num(obv_df.index.to_pydatetime()) ax.plot(_time, obv_df.obv, color='blue', lw=0.6) ax.set_ylim((min_y, max_y)) ax.yaxis.set_major_locator(mticker.MaxNLocator(nbins=5, prune='upper')) ax.text(0.024, 0.94, 'OBV', va='top', transform=ax.transAxes, fontsize=textsize) def relative_strength(self, prices, n=14): """ compute the n period relative strength indicator http://stockcharts.com/school/doku.php?id=chart_school:glossary_r#relativestrengthindex http://www.investopedia.com/terms/r/rsi.asp """ deltas = np.diff(prices) seed = deltas[:n + 1] up = seed[seed >= 0].sum() / n down = -seed[seed < 0].sum() / n rs = up / down rsi = np.zeros_like(prices) rsi[:n] = 100. - 100. / (1. + rs) for i in range(n, len(prices)): delta = deltas[i - 1] # cause the diff is 1 shorter if delta > 0: upval = delta downval = 0. else: upval = 0. downval = -delta up = (up * (n - 1) + upval) / n down = (down * (n - 1) + downval) / n rs = up / down rsi[i] = 100. - 100. / (1. + rs) return rsi def moving_average(self, x, n, type='simple'): """ compute an n period moving average. type is 'simple' | 'exponential' """ x = np.asarray(x) if type == 'simple': weights = np.ones(n) else: weights = np.exp(np.linspace(-1., 0., n)) weights /= weights.sum() a = np.convolve(x, weights, mode='full')[:len(x)] a[:n] = a[n] return a def get_indicator_messages(self, new_analysis, market_data, template): """Creates a message list from a user defined template Args: new_analysis (dict): A dictionary of data related to the analysis to send a message about. template (str): A Jinja formatted message template. Returns: list: A list with the templated messages for the notifier. """ if not self.last_analysis: self.last_analysis = new_analysis message_template = Template(template) new_messages = dict() ohlcv_values = dict() lrsi_values = dict() for exchange in new_analysis: new_messages[exchange] = dict() ohlcv_values[exchange] = dict() lrsi_values[exchange] = dict() for market_pair in new_analysis[exchange]: new_messages[exchange][market_pair] = dict() ohlcv_values[exchange][market_pair] = dict() lrsi_values[exchange][market_pair] = dict() #Getting price values for each market pair and candle period for indicator_type in new_analysis[exchange][market_pair]: if indicator_type == 'informants': for index, analysis in enumerate( new_analysis[exchange][market_pair] ['informants']['ohlcv']): values = dict() for signal in analysis['config']['signal']: values[signal] = analysis['result'].iloc[-1][ signal] ohlcv_values[exchange][market_pair][analysis[ 'config']['candle_period']] = values for index, analysis in enumerate( new_analysis[exchange][market_pair] ['informants']['lrsi']): values = dict() for signal in analysis['config']['signal']: values[signal] = analysis['result'].iloc[-1][ signal] lrsi_values[exchange][market_pair][ analysis['config']['candle_period']] = values for indicator_type in new_analysis[exchange][market_pair]: if indicator_type == 'informants': continue for indicator in new_analysis[exchange][market_pair][ indicator_type]: for index, analysis in enumerate( new_analysis[exchange][market_pair] [indicator_type][indicator]): if analysis['result'].shape[0] == 0: continue values = dict() if 'candle_period' in analysis['config']: candle_period = analysis['config'][ 'candle_period'] new_messages[exchange][market_pair][ candle_period] = list() if indicator_type == 'indicators': #Check for any user config #if candle_period not in user_indicators[indicator]: #self.logger.info('###Skipping %s %s ' % (indicator, candle_period)) #continue for signal in analysis['config']['signal']: latest_result = analysis['result'].iloc[-1] values[signal] = analysis['result'].iloc[ -1][signal] if isinstance(values[signal], float): values[signal] = format( values[signal], '.2f') elif indicator_type == 'crossovers': latest_result = analysis['result'].iloc[-1] key_signal = '{}_{}'.format( analysis['config']['key_signal'], analysis['config']['key_indicator_index']) crossed_signal = '{}_{}'.format( analysis['config']['crossed_signal'], analysis['config'] ['crossed_indicator_index']) values[key_signal] = analysis['result'].iloc[ -1][key_signal] if isinstance(values[key_signal], float): values[key_signal] = format( values[key_signal], '.2f') values[crossed_signal] = analysis[ 'result'].iloc[-1][crossed_signal] if isinstance(values[crossed_signal], float): values[crossed_signal] = format( values[crossed_signal], '.2f') status = 'neutral' if latest_result['is_hot']: status = 'hot' elif latest_result['is_cold']: status = 'cold' # Save status of indicator's new analysis new_analysis[exchange][market_pair][ indicator_type][indicator][index][ 'status'] = status if latest_result['is_hot'] or latest_result[ 'is_cold']: try: last_status = self.last_analysis[exchange][ market_pair][indicator_type][ indicator][index]['status'] except: last_status = str() should_alert = True if analysis['config'][ 'alert_frequency'] == 'once': if last_status == status: should_alert = False if not analysis['config']['alert_enabled']: should_alert = False if should_alert: base_currency, quote_currency = market_pair.split( '/') precision = market_data[exchange][ market_pair]['precision'] decimal_format = '.{}f'.format( precision['price']) candle_period = analysis['config'][ 'candle_period'] prices = '' candle_values = ohlcv_values[exchange][ market_pair] if candle_period in candle_values: for key, value in candle_values[ candle_period].items(): value = format( value, decimal_format) prices = '{} {}: {}'.format( prices, key.title(), value) lrsi = '' if candle_period in lrsi_values[exchange][ market_pair]: lrsi = lrsi_values[exchange][ market_pair][candle_period]['lrsi'] new_message = message_template.render( values=values, exchange=exchange, market=market_pair, base_currency=base_currency, quote_currency=quote_currency, indicator=indicator, indicator_number=index, analysis=analysis, status=status, last_status=last_status, prices=prices, lrsi=lrsi) new_messages[exchange][market_pair][ candle_period].append(new_message) # Merge changes from new analysis into last analysis self.last_analysis = {**self.last_analysis, **new_analysis} return new_messages