import pandas as pd import pandas_datareader.data as web from pandas_datareader._utils import RemoteDataError import numpy as np import math import matplotlib.pyplot as plt from scipy.interpolate import UnivariateSpline import datetime as dt import os import time import matplotlib as mpl # Stock Investors Financial Math # https://www.fmlabs.com/reference/default.htm?url=SimpleMA.htm # Alpha Vantage # https://www.alphavantage.co/documentation/ # Pandas # https://pandas.pydata.org/pandas-docs/stable/index.html # Knowledge # https://www.investopedia.com/articles/technical/02/050602.asp # https://ntguardian.wordpress.com/2016/09/19/introduction-stock-market-data-python-1 key = '0UO7Z2MVZ2YSQSVE' show_range_days = 5 show_symbols = ['OHB.DE', 'ITMPF', 'PLUG', 'MOR.DE', 'CSCO', 'ERCA.DE', 'AVGO', 'DIS', 'UBSFF', 'DHER.DE', 'AIR'] show_symbols = ['CSCO'] #show_symbols = ['OHB.DE'] #show_symbols = ['UBSFF'] show_symbols = ['DHER.DE'] #show_symbols = ['WDI.DE'] #show_symbols = ['EVT.DE'] show_symbols = [] k_euro = 1 / 1.11 ema_alpha = 0.75 sma_days = 10 q_days = 10 fetch_on_outdated = True mpl.rc('figure', max_open_warning = 0) symbols = { 'RIG' : {'name' : 'Transocean Ltd.', 'currency' : '$'}, 'BBIO' : {'name' : 'BridgeBio Pharma, Inc.', 'currency' : '$'}, 'APA' : {'name' : 'Apache Corporation', 'currency' : '$'}, 'CBB-PB' : {'name' : 'Cincinnati Bell Inc.', 'currency' : '$'}, 'NMHLY' : {'name' : 'NMC Health Plc', 'currency' : '$'}, 'PINS' : {'name' : 'Pinterest', 'currency' : '$'}, 'TSLA' : {'name' : 'Tesla Inc.', 'currency' : '$'}, 'ACB' : {'name' : 'Aurora Cannabis Inc.', 'currency' : '$'}, 'ITCI' : {'name' : 'Intra-Cellular Therapies', 'currency' : '$'}, 'WDI.DE' : {'name' : 'WireCard', 'currency' : '€'}, 'AAPL' : {'name' : 'Apple', 'currency' : '$'}, 'XLNX' : {'name' : 'Xilinx', 'currency' : '$'}, 'QCOM' : {'name' : 'Qualcomm', 'currency' : '$'}, 'DPW.DE' : {'name' : 'Deutsche Post', 'currency' : '€'}, 'CSCO' : {'name' : 'Cisco', 'currency' : '$'}, 'AIR' : {'name' : 'Airbus', 'currency' : '$'}, 'BA' : {'name' : 'Boeing', 'currency' : '$'}, 'NVDA' : {'name' : 'Nvidia', 'currency' : '$'}, 'MSFT' : {'name' : 'Microsoft', 'currency' : '$'}, 'DIS' : {'name' : 'Disney', 'currency' : '$'}, 'NFLX' : {'name' : 'Netflix', 'currency' : '$'}, 'OHB.DE' : {'name' : 'OHB', 'currency' : '€'}, 'ERCA.DE' : {'name' : 'Ericsson', 'currency' : '€'}, 'VAR1.DE' : {'name' : 'Varta', 'currency' : '€'}, 'HD' : {'name' : 'Home Depot', 'currency' : '$'}, 'AMZN' : {'name' : 'Amazon', 'currency' : '$'}, 'GOOGL' : {'name' : 'Google', 'currency' : '$'}, 'FB2A.DE' : {'name': 'Facebook Inc.', 'currency' : '€'}, 'ZIL2.DE' : {'name': 'ElringKlinger', 'currency' : '€'}, 'SIS.DE' : {'name': 'First Sensor', 'currency' : '€'}, 'SIE.DE' : {'name': 'Siemens', 'currency' : '€'}, 'GFT.DE' : {'name': 'GFT Technologies', 'currency' : '€'}, 'AMD.DE' : {'name': 'Advanced Micro Devices', 'currency' : '€'}, 'CAP.DE' : {'name': 'Encavis', 'currency' : '€'}, 'ADBE' : {'name': 'Adobe', 'currency' : '$'}, 'PANW' : {'name': 'Palo Alto Networks', 'currency' : '$'}, 'AMAT' : {'name': 'Applied Materials', 'currency' : '$'}, 'RIB.DE' : {'name': 'RIB Software', 'currency' : '€'}, 'WAF.DE' : {'name': 'Siltronic', 'currency' : '€'}, 'EVT.DE' : {'name': 'Evotec', 'currency' : '€'}, 'VOW.DE' : {'name': 'Volkswagen', 'currency' : '€'}, 'BMW.DE' : {'name': 'BMW', 'currency' : '€'}, 'NSU.DE' : {'name': 'Audi', 'currency' : '€'}, 'DAI.DE' : {'name': 'Daimler', 'currency' : '€'}, 'SHA.DE' : {'name': 'Schaeffler', 'currency' : '€'}, 'CON.DE' : {'name': 'Continental', 'currency' : '€'}, 'DHER.DE' : {'name': 'Delivery Hero', 'currency' : '€'}, 'BSL.DE' : {'name': 'Basler', 'currency' : '€'}, 'D6H.DE' : {'name': 'DATAGROUP', 'currency' : '€'}, 'TC1.DE' : {'name': 'Tele Columbus', 'currency' : '€'}, 'VODI.DE' : {'name': 'Vodaphone Group', 'currency' : '€'}, 'ATVI' : {'name': 'Activision Blizzard', 'currency' : '$'}, 'GME' : {'name': 'GameStop', 'currency' : '$'}, 'NTO.F' : {'name': 'Nintendo', 'currency' : '$'}, 'UBSFF' : {'name': 'UBI Soft', 'currency' : '$'}, 'NXPRF' : {'name': 'Nexans', 'currency' : '$'}, # 'IMPUF' : {'name': 'Impala Platinum Holdings', 'currency' : '$'}, 'NMPNF' : {'name': 'Northam Platinum', 'currency' : '$'}, 'ALSMY' : {'name': 'Alstom', 'currency' : '$'}, 'ARRD.F' : {'name': 'Arcelor Mittal', 'currency' : '$'}, 'PHG' : {'name': 'Philips', 'currency' : '$'}, 'KEYS' : {'name': 'KeySight', 'currency' : '$'}, 'ORCL' : {'name': 'Oracle', 'currency' : '$'}, 'UBER' : {'name': 'Uber', 'currency' : '$'}, 'VMW' : {'name': 'VMWare', 'currency' : '$'}, 'AVGO' : {'name' : 'Avago', 'currency' : '$'}, 'CY' : {'name': 'Cypress Semiconductor', 'currency' : '$'}, 'QABSY' : {'name': 'Qanta Airways', 'currency' : '$'}, 'BLDP' : {'name': 'Ballard Power', 'currency' : '$'}, 'D7G.F' : {'name': 'Nel ASA', 'currency' : '$'}, 'ITMPF' : {'name': 'ITM Power', 'currency' : '$'}, 'PLUG' : {'name': 'PlugPower', 'currency' : '$'}, 'PCELF' : {'name': 'PowerCell', 'currency' : '$'}, 'SU.PA' : {'name': 'Schneider Electric', 'currency' : '$'}, # 'SON1.DE' : {'name': '', 'currency' : '€'}, 'STM.DE' : {'name': 'STM Micro', 'currency' : '€'}, 'BC8.DE' : {'name': 'Bechtle', 'currency' : '€'}, 'MOR.DE' : {'name' : 'Morphosys', 'currency' : '€'}, 'BAYN.DE' : {'name' : 'Bayer', 'currency' : '€'}, 'BEI.DE' : {'name' : 'Beiersdorf', 'currency' : '€'}, 'EUZ.DE' : {'name': 'Eckert & Ziegler', 'currency' : '€'}, 'SLAB' : {'name': 'Silicon Laboratories', 'currency' : '$'}, 'SPLK' : {'name' : 'Splunk', 'currency' : '$'}, 'IAG' : {'name' : 'IAG', 'currency' : '$'}, 'INTC' : {'name' : 'Intel Corporation', 'currency' : '$'}, 'I' : {'name': 'IntelSat', 'currency' : '$'}} def exponential_moving_average(data, key, alpha=0.5, ic=None): result = np.zeros_like(data[key]) if ic is None: r = data[key][0] else: r = ic n = 0 for v in data[key]: r = alpha*r + (1-alpha)*v result[n] = r n += 1 return np.array(result) def moving_average(data, key, window_days, ic=0): mem = [ic] * window_days result = np.zeros_like(data[key]) k=0 cumsum = ic n = 0 for v in data[key]: cumsum += (v - mem[k]) mem[k] = v k += 1 if k >= window_days: k=0 result[n] = cumsum/window_days n += 1 return result def moving_variance(data, key, window_days, ic=0): mem = [ic] * window_days result = np.zeros_like(data[key]) k=0 cumsum = ic data_mean = data[key] - moving_average(data, key, window_days, ic) n = 0 for v in data_mean: try: v2 = v * v except: print ('v', v) cumsum += (v2 - mem[k]) mem[k] = v2 k += 1 if k >= window_days: k=0 var = max(0, cumsum) / window_days result[n] = math.sqrt(var) n += 1 return result def moving_max(data, key, window_days, ic=-1e9): mem = [ic] * window_days result = np.zeros_like(data[key]) k=0 n = 0 for v in data[key]: mem[k] = v k += 1 if k >= window_days: k=0 result[n] = np.max(mem) n += 1 return result def moving_min(data, key, window_days, ic=1e9): mem = [ic] * window_days result = np.zeros_like(data[key]) k=0 n = 0 for v in data[key]: mem[k] = v k += 1 if k >= window_days: k=0 result[n] = np.min(mem) n += 1 return result def normalize(data, key, ic=None): result = np.zeros_like(data[key]) cumsum = 0 if ic is None: prev = data[key][0] else: prev = ic n = 0 for v in data[key]: if prev == 0: print (prev) cumsum += 100*(v - prev)/(prev) result[n] = cumsum if v > 0: prev = v n += 1 return result def colsum(data, keys): result = np.zeros(data['index'].shape) for key in keys: result += data[key] return result def macd(data, key): ema_short = exponential_moving_average(data, key, alpha=0.85) ema_long = exponential_moving_average(data, key, alpha=0.925) return ema_short - ema_long def bollinger(data, window_days, f=2): tp = colsum(data, keys=['high', 'low', 'close']) / 3 tp_dict = {'tp': tp} stddev = np.array(moving_variance(tp_dict, 'tp', window_days)) mid = np.array(moving_average(tp_dict, key='tp', window_days=window_days)) upper = mid + f * stddev lower = mid - f * stddev result = {'index' : data['index'], 'upper' : upper, 'mid' : mid, 'lower' : lower} return result def agent_buy(name, data, range_days, cand_window, marker_key='close_n', thresh_max=-10, thresh_min=1, buy_callback=None): N = len(data['index']) buy_list = {'index' : np.array([None]*N), 'Buy_dip' : np.array([None]*N)} cand = None do_buy = False for n in range(N-range_days, N): vmax = data['Qmax'][n] vmin = data['Qmin'][n] trend = data['macd_fdd'][n] index = data['index'][n] value = data[marker_key][n] if vmin <= thresh_min: if vmax <= thresh_max: cand = n if trend >= 0: do_buy = True print ("{}: Buy on {} at {:0.2f}".format(name, index, value)) cand = None if cand is not None: if n - cand <= cand_window: if trend >= 0: do_buy = True former = data[marker_key][cand] print("{}: Delayed buy on {} at {:0.2f} ({:0.2f})".format(name, index, value, former-value)) cand = None else: cand = None if do_buy: do_buy = False buy_list['index'][n] = index buy_list['Buy_dip'][n] = value if buy_callback is not None: buy_callback({'name': name, 'item': {'value': value, 'date': index}}) return buy_list class Title(object): def __init__(self, symbol, params): self.symbol = symbol self.params = params try: self.name = params['name'] except: self.name = symbol self.ax = None self.fig = None self.currency = params['currency'] self.currency_corr = 1 if '$' in self.currency: self.currency_corr = k_euro self.data = {} self.boll = {} self.indicators = {} def fetch(self): filename = self.symbol.replace('.', '_') + '.h5' fetch = False today = dt.date.today() start = dt.datetime(today.year, 1, 1) end = today try: hdf = pd.HDFStore(filename, 'r') hdf.close() except: fetch = True try: lastmodified = dt.datetime.fromtimestamp(os.stat(filename).st_mtime).date() if lastmodified != today: fetch = fetch_on_outdated except: fetch = True if fetch: # Get stock price via data reader while(True): try: print("Fetching \"{}\"".format(self.symbol)) data = web.DataReader(self.symbol, "av-daily", start, end, api_key=key) break except RemoteDataError: for timeout in reversed(range(0, 60)): print ("Try again in {} s".format(timeout)) time.sleep(1) hdf = pd.HDFStore(filename) hdf[self.symbol] = data else: hdf = pd.HDFStore(filename, 'r') data = hdf[self.symbol] * self.currency_corr N = len(data.index) self.data['index'] = np.array(data.index[0:N]) self.data['close'] = np.array(data['close'][0:N]) self.data['high'] = np.array(data['high'][0:N]) self.data['low'] = np.array(data['low'][0:N]) hdf.close() def get_latest(self, key): N = len(self.data['index']) return self.data[key][N-1] def statistics(self): N = len(self.data['index']) self.data['ema'] = exponential_moving_average(self.data, key='close', alpha=ema_alpha) self.data['sma'] = moving_average(self.data, key='close', window_days=sma_days) self.data['close_n'] = normalize(self.data, key='close') self.data['macd'] = macd(self.data, key='close_n') self.data['min'] = moving_min(self.data, key='close_n', window_days=q_days) self.data['max'] = moving_max(self.data, key='close_n', window_days=q_days) self.data['Qmin'] = self.data['close_n'] - self.data['min'] self.data['Qmax'] = self.data['close_n'] - self.data['max'] self.boll = bollinger(self.data, window_days=30) x_r = np.linspace(0, N, N) y_r = self.data['macd'] spl = UnivariateSpline(x_r, y_r) spl.set_smoothing_factor(0.25) spl_d = spl.derivative() spl_dd = spl_d.derivative() yf = spl(x_r) yf_d = spl_d(x_r) yf_dd = spl_dd(x_r) self.data['macd_f'] = np.transpose(yf) self.data['macd_fd'] = np.transpose(yf_d) self.data['macd_fdd'] = np.transpose(yf_dd) def analyze(self, buy_callback, range_days): return agent_buy(self.symbol, self.data, marker_key='close_n', cand_window=5, range_days=range_days, buy_callback=buy_callback) @staticmethod def has_candidate(data, key): result = np.count_nonzero(data[key] != None) return result > 0 @staticmethod def slice(data, start, stop): result = {} for key in iter(data): result[key] = data[key][start:stop] return result def __plot(self, data, keys): N = len(data['index']) start = max(0, N - show_range_days) stop = N xr = list(range(start, stop)) sliced = Title.slice(data, start, stop) for key in keys: plt.plot(xr, sliced[key], label=key) def hover(event): if event.inaxes == self.ax: x_idx = min(N-1, int(event.xdata)) self.ax.format_xdata = lambda x: self.data['index'][x_idx] self.ax.format_ydata = lambda y: '{:.2f}%'.format(self.data['close_n'][x_idx]) self.fig.canvas.mpl_connect("motion_notify_event", hover) def __ind(self, data, keys): N = len(data['index']) start = max(0, N - show_range_days) stop = N xr = list(range(start, stop)) sliced = Title.slice(data, start, stop) for key in keys: plt.plot(xr, sliced[key], 'go', label=key) def show(self, indicators, figNum=1): num_subplots = 4 self.fig = plt.figure(figNum) self.ax = plt.subplot(100*num_subplots + 10 + 1) self.__plot(self.data, ['min', 'max', 'close_n']) self.__ind(indicators, ['Buy_dip']) plt.title('{} ({})'.format(self.name, self.symbol)) plt.legend() plt.grid() self.ax = plt.subplot(100*num_subplots + 10 + 2) # self.__plot(self.boll, ['lower', 'mid', 'upper']) self.__plot(self.data, ['Qmin', 'Qmax']) plt.legend() plt.grid() self.ax = plt.subplot(100*num_subplots + 10 + 3) self.__plot(self.data, ['macd', 'macd_f']) plt.legend() plt.grid() self.ax = plt.subplot(100*num_subplots + 10 + 4) self.__plot(self.data, ['macd_fd', 'macd_fdd']) plt.legend() plt.grid() buy_list = {} def buy_callback(data): name = data['name'] item = data['item'] if name not in buy_list: buy_list[name] = {'items': [item]} else: buy_list[name]['items'].append(item) print(data) figNum = 1 if len(show_symbols) > 0: for symbol in show_symbols: if symbol in symbols: title = Title(symbol, symbols[symbol]) title.fetch() title.statistics() ind = title.analyze(buy_callback, range_days=show_range_days) title.show(ind, figNum=figNum) figNum += 1 else: for symbol in symbols: title = Title(symbol, symbols[symbol]) title.fetch() title.statistics() ind = title.analyze(buy_callback, range_days=show_range_days) if Title.has_candidate(ind, 'Buy_dip'): print('-----------------------------------------------') title.show(ind, figNum=figNum) figNum += 1 gain_accum = 0 num_stocks = 0 for symbol in buy_list: items = buy_list[symbol]['items'] title = Title(symbol, symbols[symbol]) title.fetch() title.statistics() gain = title.get_latest('close_n') - items[0]['value'] gain_accum += gain num_stocks += 1 print ('{}: Gain = {} %'.format(symbol, gain)) if num_stocks > 0: print ('Gain total = {} %'.format(gain_accum/num_stocks)) plt.show()