def kst_signal(fund: pd.DataFrame, **kwargs) -> list: """Know Sure Thing - Signal Also known as the "Summed Rate of Change" Oscillator Arguments: fund {pd.DataFrame} -- fund dataset Optional Args: periods {list} -- ROC periods (default: {[10, 15, 20, 30]}) sma_intervals {list} -- sma intervals corresponding to the ROC periods (default: {[10, 10, 10, 15]}) plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) views {str} -- (default: {''}) p_bar {ProgressBar} -- (default: {None}) Returns: list -- kst signal and its 9d sma signal line """ periods = kwargs.get('periods', [10, 15, 20, 30]) sma_intervals = kwargs.get('sma_intervals', [10, 10, 10, 15]) plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') views = kwargs.get('views', '') p_bar = kwargs.get('p_bar') increment = 0.7 / float(len(periods) * 3) signal = [0.0] * len(fund['Close']) for i, period in enumerate(periods): roc = roc_signal(fund, period) if p_bar: p_bar.uptick(increment=increment) sma = simple_moving_avg(roc, sma_intervals[i], data_type='list') if p_bar: p_bar.uptick(increment=increment) for j in range(len(signal)): signal[j] += float(i + 1) * sma[j] if p_bar: p_bar.uptick(increment=increment) signal_line = simple_moving_avg(signal, 9, data_type='list') if p_bar: p_bar.uptick(increment=0.1) name2 = INDEXES.get(name, name) title = f"{name2} - Know Sure Thing" if plot_output: dual_plotting(fund['Close'], [signal, signal_line], 'Price', 'KST', title=title) else: filename = os.path.join(name, views, f"kst_oscillator_{name}") dual_plotting(fund['Close'], [signal, signal_line], 'Price', 'KST', title=title, saveFig=True, filename=filename) return signal, signal_line
def cci_metrics(position: pd.DataFrame, cci: dict, **kwargs) -> list: plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view', '') metrics = [0.0] * len(position['Close']) features = cci['signals'] signals = cci['tabular'] periods = cci['periods'] for feat in features: if 'zero' in feat['value']: if feat['type'] == 'bullish': metrics[feat['index']] += 0.6 else: metrics[feat['index']] -= 0.6 elif '100' in feat['value']: if feat['type'] == 'bullish': metrics[feat['index']] += 1.0 else: metrics[feat['index']] -= 1.0 for period in signals: if period == str(periods['short']): point = 0.2 elif period == str(periods['medium']): point = 0.2 else: point = 0.2 for i, value in enumerate(signals[period]): if value > 100.0: metrics[i] += point if value < 100.0: metrics[i] -= point metrics = exponential_moving_avg(metrics, 7, data_type='list') norm = normalize_signals([metrics]) metrics = norm[0] name2 = INDEXES.get(name, name) title = f"{name2} - Commodity Channel Index Metrics" if plot_output: dual_plotting(position['Close'], metrics, 'Price', 'Metrics', title=title) else: filename = filename = os.path.join( name, view, f"commodity_metrics_{name}.png") dual_plotting(position['Close'], metrics, 'Price', 'Metrics', title=title, saveFig=True, filename=filename) return metrics
def volatility_calculation(position: pd.DataFrame, **kwargs) -> list: """Volatility Calculation Arguments: position {pd.DataFrame} -- fund dataset Optional Args: plot_output {bool} -- (default: {True}) Returns: list -- volatility data as list of weighted standard deviations """ plot_output = kwargs.get('plot_output', True) periods = [50, 100, 250, 500] stdevs = [] for _ in range(len(periods)): std = [0.0] * len(position['Close']) stdevs.append(std) typical_price = [] for i, close in enumerate(position['Close']): typical_price.append( (close + position['Low'][i] + position['High'][i]) / 3.0) for i, period in enumerate(periods): for j in range(period, len(typical_price)): std = np.std(typical_price[j - (period):j]) * 2.0 stdevs[i][j] = std std_correction = [] for j in range(len(typical_price)): if j < periods[1]: s = stdevs[0][j] elif j < periods[2]: s = 0.7 * stdevs[1][j] + 0.3 * stdevs[0][j] elif j < periods[3]: s = 0.55 * stdevs[2][j] + \ 0.3 * stdevs[1][j] + 0.15 * stdevs[0][j] else: s = 0.4 * stdevs[3][j] + 0.3 * stdevs[2][j] + \ 0.2 * stdevs[1][j] + 0.1 * stdevs[0][j] std_correction.append(s) for i, price in enumerate(typical_price): std_correction[i] = std_correction[i] / price if plot_output: dual_plotting(position['Close'], std_correction, 'Price', 'Volatility', title='Standard Deviation Volatility') return std_correction
def generate_momentum_signal(position: pd.DataFrame, **kwargs) -> list: """Generate Momentum Signal https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cmo Arguments: position {pd.DataFrame} -- fund dataset Optional Args: interval {int} -- lookback period (default: {20}) plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- (default: {''}) Returns: list -- momentum signal """ interval = kwargs.get('interval', 20) plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view', '') signal = [] for i in range(interval - 1): signal.append(0.0) for i in range(interval - 1, len(position['Close'])): sum_up = 0.0 sum_down = 0.0 for j in range(i - (interval - 2), i): if position['Close'][j] > position['Close'][j - 1]: sum_up += position['Close'][j] - position['Close'][j - 1] else: sum_down += np.abs(position['Close'][j] - position['Close'][j - 1]) cmo = 100.0 * (sum_up - sum_down) / (sum_up + sum_down) signal.append(cmo) name2 = INDEXES.get(name, name) if plot_output: dual_plotting(position['Close'], signal, 'Price', 'CMO', title=f'{name2} - (Chande) Momentum Oscillator') else: filename = os.path.join(name, view, f"momentum_oscillator_{name}.png") dual_plotting(position['Close'], signal, 'Price', 'CMO', title='(Chande) Momentum Oscillator', filename=filename, saveFig=True) return signal
def get_atr_signal(fund: pd.DataFrame, **kwargs) -> list: """Get ATR Signal Arguments: fund {pd.DataFrame} -- fund dataset Optional Args: period {int} -- lookback period of atr signal (default: {14}) plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) views {str} -- (default: {''}) out_suppress {bool} -- (default: {False}) Returns: list -- atr signal """ period = kwargs.get('period', 14) plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') views = kwargs.get('views', '') out_suppress = kwargs.get('out_suppress', False) signal = [0.0] * len(fund['Close']) atr = [0.0] for i in range(1, len(fund['Close'])): trues = [ fund['High'][i] - fund['Low'][i], abs(fund['High'][i] - fund['Close'][i - 1]), abs(fund['Low'][i] - fund['Close'][i - 1]) ] _max = max(trues) atr.append(_max) for i in range(period - 1, len(fund['Close'])): atr_val = sum(atr[i - (period - 1):i + 1]) / float(period) signal[i] = atr_val if not out_suppress: name2 = INDEXES.get(name, name) title = f"{name2} - Average True Range" if plot_output: dual_plotting(fund['Close'], signal, 'Price', 'ATR', title=title) else: filename = os.path.join(name, views, f"atr_{name}.png") dual_plotting(fund['Close'], signal, 'Price', 'ATR', title=title, saveFig=True, filename=filename) return signal
def mov_avg_convergence_divergence(fund: pd.DataFrame, **kwargs) -> dict: """Moving Average Convergence Divergence (MACD) Arguments: fund {pd.DataFrame} -- fund historical data Optional Args: name {str} -- name of fund, primarily for plotting (default: {''}) plot_output {bool} -- True to render plot in realtime (default: {True}) progress_bar {ProgressBar} -- (default: {None}) view {str} -- directory of plots (default: {''}) Returns: dict -- contains all macd information in regarding macd """ name = kwargs.get('name', '') plot_output = kwargs.get('plot_output', True) progress_bar = kwargs.get('progress_bar', None) view = kwargs.get('view', '') macd = generate_macd_signal( fund, plot_output=plot_output, name=name, view=view) if progress_bar is not None: progress_bar.uptick(increment=0.3) macd = get_macd_statistics(macd, progress_bar=progress_bar) macd = macd_metrics(fund, macd, p_bar=progress_bar, name=name, plot_output=plot_output, view=view) macd_sig = macd['tabular']['macd'] sig_line = macd['tabular']['signal_line'] name3 = INDEXES.get(name, name) name2 = name3 + ' - MACD' if plot_output: dual_plotting(fund['Close'], [macd_sig, sig_line], 'Position Price', ['MACD', 'Signal Line'], title=name2) print_macd_statistics(macd) else: filename = os.path.join(name, view, f"macd_{name}.png") dual_plotting(fund['Close'], [macd_sig, sig_line], 'Position Price', ['MACD', 'Signal Line'], title=name2, saveFig=True, filename=filename) if progress_bar is not None: progress_bar.uptick(increment=0.3) macd['type'] = 'oscillator' macd['signals'] = get_macd_features(macd, fund) macd['length_of_data'] = len(macd['tabular']['macd']) return macd
def kst_metrics(fund: pd.DataFrame, kst_dict: dict, **kwargs) -> dict: """KST Metrics Arguments: fund {pd.DataFrame} -- fund dataset kst_dict {dict} -- kst data object Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) views {str} -- (default: {''}) Returns: dict -- kst data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') views = kwargs.get('views', '') metrics = [0.0] * len(kst_dict['tabular']['signal']) for sig in kst_dict['signals']: if sig['type'] == 'bullish': multiply = 1.0 else: multiply = -1.0 if 'crossover' in sig['value']: metrics[sig['index']] += 0.2 * multiply if 'crossed' in sig['value']: metrics[sig['index']] += 1.0 * multiply metrics = exponential_moving_avg(metrics, 7, data_type='list') metrics = normalize_signals([metrics])[0] kst_dict['metrics'] = metrics name2 = INDEXES.get(name, name) title = f"{name2} - KST Metrics" if plot_output: dual_plotting(fund['Close'], kst_dict['metrics'], 'Price', 'Metrics', title=title) else: filename = os.path.join(name, views, f"kst_metrics_{name}") dual_plotting(fund['Close'], kst_dict['metrics'], 'Price', 'Metrics', title=title, saveFig=True, filename=filename) return kst_dict
def compare_against_signal_line(signal: list, **kwargs) -> list: """Compare Against Signal Line Compare momentum oscillator signal vs. sma signal line Arguments: signal {list} -- momentum oscillator signal Optional Args: plot_output {bool} -- (default: {True}) interval {int} -- lookback for simple moving average (default: {9}) position {pd.DataFrame} -- fund dataset (default: {[]}) Returns: list -- bear_bull oscillator signal """ plot_output = kwargs.get('plot_output', True) interval = kwargs.get('interval', 9) position = kwargs.get('position', []) signal_line = simple_moving_avg(signal, interval, data_type='list') bear_bull = [] for i in range(len(signal)): if signal[i] > signal_line[i]: bear_bull.append(1.0) elif signal[i] < signal_line[i]: bear_bull.append(-1.0) else: bear_bull.append(0.0) if plot_output and (len(position) > 0): dual_plotting(position['Close'], bear_bull, 'Price', 'Bear_Bull', title='Bear Bull') return bear_bull
def adx_metrics(fund: pd.DataFrame, adx: dict, **kwargs) -> dict: """ADX Metricx Arguments: fund {pd.DataFrame} -- fund dataset adx {dict} -- adx data object Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- (default: {''}) pbar {ProgressBar} -- (default: {None}) Returns: dict -- adx data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view', '') pbar = kwargs.get('pbar') adx['metrics'] = [0.0] * len(fund['Close']) adx['signals'] = [] auto = autotrend(fund['Close'], periods=[14]) no_trend = adx['tabular']['no_trend'][0] strong_trend = adx['tabular']['strong_trend'][0] over_trend = adx['tabular']['high_trend'][0] signal = adx['tabular']['adx'] for i in range(1, len(signal)): data = None date = fund.index[i].strftime("%Y-%m-%d") trend = 0.0 state = 'none' if auto[i] > 0.0: trend = 1.0 state = 'bull' elif auto[i] < 0.0: trend = -1.0 state = 'bear' if signal[i - 1] < no_trend and signal[i] > no_trend: adx['metrics'][i] += 1.0 * trend if state != 'none': data = { "type": state, "value": '20-crossover: trend start', "index": i, "date": date } if signal[i - 1] < strong_trend and signal[i] > strong_trend: adx['metrics'][i] += 0.6 * trend if state != 'none': data = { "type": state, "value": '25-crossover: STRONG trend', "index": i, "date": date } if signal[i] > strong_trend: adx['metrics'][i] += 0.2 * trend if signal[i - 1] > over_trend and signal[i] < over_trend: adx['metrics'][i] += -1.0 * trend if state != 'none': if state == 'bull': state = 'bear' else: state = 'bull' data = { "type": state, "value": '40-crossunder: weakening trend', "index": i, "date": date } if signal[i - 1] > no_trend and signal[i] < no_trend: adx['metrics'][i] += 0.0 if state != 'none': if state == 'bull': state = 'bear' else: state = 'bull' data = { "type": state, "value": '20-crossunder: WEAK/NO trend', "index": i, "date": date } if data is not None: adx['signals'].append(data) if pbar is not None: pbar.uptick(increment=0.1) metrics = exponential_moving_avg(adx['metrics'], 7, data_type='list') if pbar is not None: pbar.uptick(increment=0.1) adx['metrics'] = normalize_signals([metrics])[0] name2 = INDEXES.get(name, name) title = f"{name2} - ADX Metrics" if plot_output: dual_plotting(fund['Close'], adx['metrics'], 'Price', 'Metrics', title=title) else: filename = os.path.join(name, view, f"adx_metrics_{name}.png") dual_plotting(fund['Close'], adx['metrics'], 'Price', 'Metrics', title=title, saveFig=True, filename=filename) if pbar is not None: pbar.uptick(increment=0.2) return adx
def clustered_metrics(position: pd.DataFrame, cluster_oscs: dict, **kwargs) -> dict: """Clustered Metrics Arguments: position {pd.DataFrame} -- dataset cluster_oscs {dict} -- clustered osc data object Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- file directory of plots (default: {''}) Returns: dict -- clustered osc data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view') ults = cluster_oscs['tabular'] # Take indicator set: weight, filter, normalize weights = [1.0, 0.85, 0.55, 0.1] state2 = [0.0] * len(ults) for ind, s in enumerate(ults): if s != 0.0: state2[ind] += s # Smooth the curves if ind - 1 >= 0: state2[ind - 1] += s * weights[1] if ind + 1 < len(ults): state2[ind + 1] += s * weights[1] if ind - 2 >= 0: state2[ind - 2] += s * weights[2] if ind + 2 < len(ults): state2[ind + 2] += s * weights[2] if ind - 3 >= 0: state2[ind - 3] += s * weights[3] if ind + 3 < len(ults): state2[ind + 3] += s * weights[3] metrics = exponential_moving_avg(state2, 7, data_type='list') norm = normalize_signals([metrics]) metrics = norm[0] cluster_oscs['metrics'] = metrics name3 = INDEXES.get(name, name) name2 = name3 + " - Clustered Oscillator Metrics" if plot_output: dual_plotting(position['Close'], metrics, 'Price', 'Metrics', title=name2) else: filename = os.path.join(name, view, f"clustered_osc_metrics_{name}.png") dual_plotting(position['Close'], metrics, 'Price', 'Metrics', title=name2, filename=filename, saveFig=True) return cluster_oscs
def cluster_oscs(position: pd.DataFrame, **kwargs): """ 2-3-5-8 multiplier comparing several different osc lengths Arguments: position {pd.DataFrame} -- list of y-value datasets to be plotted (multiple) Optional Args: name {str} -- name of fund, primarily for plotting (default: {''}) plot_output {bool} -- True to render plot in realtime (default: {True}) function {str} -- type of oscillator (default: {'full_stochastic'}) (others: ultimate, rsi, all, market) wma {bool} -- output signal is filtered by windowed moving average (default: {True}) progress_bar {ProgressBar} -- (default: {None}) view {str} -- file directory of plots (default: {''}) Returns: list -- dict of all clustered oscillator info, list of clustered osc signal """ name = kwargs.get('name', '') plot_output = kwargs.get('plot_output', True) function = kwargs.get('function', 'full_stochastic') wma = kwargs.get('wma', True) prog_bar = kwargs.get('progress_bar', None) view = kwargs.get('view', '') cluster_oscs = {} clusters = generate_cluster(position, function, p_bar=prog_bar) cluster_oscs['tabular'] = clusters cluster_oscs['length_of_data'] = len(clusters) #clusters_filtered = cluster_filtering(clusters, filter_thresh) clusters_wma = exponential_moving_avg(clusters, interval=3, data_type='list') if prog_bar is not None: prog_bar.uptick(increment=0.1) dates = cluster_dates(clusters_wma, position) if prog_bar is not None: prog_bar.uptick(increment=0.1) signals = clustered_signals(dates) if prog_bar is not None: prog_bar.uptick(increment=0.1) cluster_oscs['clustered type'] = function cluster_oscs[function] = dates cluster_oscs['signals'] = signals cluster_oscs = clustered_metrics(position, cluster_oscs, plot_output=plot_output, name=name, view=view) name3 = INDEXES.get(name, name) name2 = name3 + ' - Clustering: ' + function if plot_output: dual_plotting(position['Close'], clusters, 'Position Price', 'Clustered Oscillator', x_label='Trading Days', title=name2) else: filename = os.path.join(name, view, f"clustering_{name}_{function}") dual_plotting(y1=position['Close'], y2=clusters, y1_label='Price', y2_label='Clustered Oscillator', x_label='Trading Days', title=name2, saveFig=True, filename=filename) if prog_bar is not None: prog_bar.uptick(increment=0.2) if wma: cluster_oscs['tabular'] = clusters_wma cluster_oscs['type'] = 'oscillator' return cluster_oscs
def bear_bull_feature_detection(bear_bull: dict, position: pd.DataFrame, **kwargs) -> dict: """Bear Bull Feature Detection Arguments: bear_bull {dict} -- bull bear data object position {pd.DataFrame} -- dataset Optional Args: interval {int} -- size of exponential moving average window (default: {13}) bb_interval {list} -- list of sizes of bear and bull windows (default: {[4, 5, 6, 7, 8]}) plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) p_bar {ProgressBar} -- (default {None}) view {str} -- (default: {''}) Returns: dict -- [description] """ interval = kwargs.get('interval', 13) bb_interval = kwargs.get('bb_interval', [4, 5, 6, 7, 8]) plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') p_bar = kwargs.get('p_bar') view = kwargs.get('view') # Determine the slope of the ema at given points ema = exponential_moving_avg(position, interval) ema_slopes = [0.0] * (interval-1) for i in range(interval-1, len(ema)): x = list(range(i-(interval-1), i)) y = ema[i-(interval-1): i] reg = linregress(x, y=y) ema_slopes.append(reg[0]) if p_bar is not None: p_bar.uptick(increment=0.1) incr = 0.6 / float(len(bb_interval)) state = [0.0] * len(ema) features = [] for bb in bb_interval: # Determine the slope of the bear power signal at given points bear_slopes = [0.0] * (bb-1) for i in range(bb-1, len(ema)): x = list(range(i-(bb-1), i)) y = bear_bull['tabular']['bears'][i-(bb-1): i] reg = linregress(x, y=y) bear_slopes.append(reg[0]) # Determine the slope of the bull power signal at given points bull_slopes = [0.0] * (bb-1) for i in range(bb-1, len(ema)): x = list(range(i-(bb-1), i)) y = bear_bull['tabular']['bulls'][i-(bb-1): i] reg = linregress(x, y=y) bull_slopes.append(reg[0]) for i in range(1, len(ema)): data = None date = position.index[i].strftime("%Y-%m-%d") if ema_slopes[i] > 0.0: if bear_bull['tabular']['bears'][i] < 0.0: if bear_slopes[i] > 0.0: # Determine the basic (first 2 conditions) bullish state (+1) state[i] += 1.0 if bear_bull['tabular']['bulls'][i] > bear_bull['tabular']['bulls'][i-1]: state[i] += 0.5 if position['Close'][i] < position['Close'][i-1]: # Need to find bullish divergence! state[i] += 1.0 data = { "type": 'bullish', "value": f'bullish divergence: {bb}d interval', "index": i, "date": date } if ema_slopes[i] < 0.0: if bear_bull['tabular']['bulls'][i] > 0.0: if bull_slopes[i] < 0.0: # Determine the basic (first 2 conditions) bearish state (+1) state[i] += -1.0 if bear_bull['tabular']['bears'][i] < bear_bull['tabular']['bears'][i-1]: state[i] += -0.5 if position['Close'][i] > position['Close'][i-1]: # Need to find bearish divergence! state[i] += -1.0 data = { "type": 'bearish', "value": f'bearish divergence: {bb}d interval', "index": i, "date": date } if data is not None: features.append(data) if p_bar is not None: p_bar.uptick(increment=incr) bear_bull['signals'] = filter_features(features, plot_output=plot_output) bear_bull['length_of_data'] = len(bear_bull['tabular']['bears']) weights = [1.0, 0.75, 0.45, 0.1] state2 = [0.0] * len(state) for ind, s in enumerate(state): if s != 0.0: state2[ind] += s # Smooth the curves if ind - 1 >= 0: state2[ind-1] += s * weights[1] if ind + 1 < len(state): state2[ind+1] += s * weights[1] if ind - 2 >= 0: state2[ind-2] += s * weights[2] if ind + 2 < len(state): state2[ind+2] += s * weights[2] if ind - 3 >= 0: state2[ind-3] += s * weights[3] if ind + 3 < len(state): state2[ind+3] += s * weights[3] state3 = exponential_moving_avg(state2, 7, data_type='list') norm = normalize_signals([state3]) state4 = norm[0] title = 'Bear Bull Power Metrics' if plot_output: dual_plotting(position['Close'], state4, 'Price', 'Bear Bull Power', title=title) else: filename = os.path.join(name, view, f"bear_bull_power_{name}.png") dual_plotting(position['Close'], state4, 'Price', 'Metrics', title=title, saveFig=True, filename=filename) bear_bull['metrics'] = state4 if p_bar is not None: p_bar.uptick(increment=0.1) return bear_bull
def get_ao_signal(position: pd.DataFrame, **kwargs) -> list: """Get Awesome Oscillator Signal Arguments: position {pd.DataFrame} -- dataset of fund Optional Args: short_period {int} -- moving average period (default: {5}) long_period {int} -- moving average period (default: {34}) filter_style {str} -- moving average type, 'sma' or 'ema' (default: {'sma'}) plot_output {bool} -- True plots in realtime (default: {True}) p_bar {ProgressBar} -- (default: {None}) name {str} -- name of fund (default: {''}) Returns: list -- awesome signal """ short_period = kwargs.get('short_period', 5) long_period = kwargs.get('long_period', 34) filter_style = kwargs.get('filter_style', 'sma') plot_output = kwargs.get('plot_output', True) p_bar = kwargs.get('progress_bar') name = kwargs.get('name', '') view = kwargs.get('view') signal = [] mid_points = [] for i, high in enumerate(position['High']): mid = (high + position['Low'][i]) / 2 mid_points.append(mid) if p_bar is not None: p_bar.uptick(increment=0.1) if filter_style == 'sma': short_signal = simple_moving_avg(mid_points, short_period, data_type='list') long_signal = simple_moving_avg(mid_points, long_period, data_type='list') elif filter_style == 'ema': short_signal = exponential_moving_avg(mid_points, short_period, data_type='list') long_signal = exponential_moving_avg(mid_points, long_period, data_type='list') else: short_signal = [] long_signal = [] if p_bar is not None: p_bar.uptick(increment=0.1) for i in range(long_period): signal.append(0.0) for i in range(long_period, len(long_signal)): diff = short_signal[i] - long_signal[i] signal.append(diff) if p_bar is not None: p_bar.uptick(increment=0.1) med_term = simple_moving_avg(signal, 14, data_type='list') long_term = simple_moving_avg(signal, long_period, data_type='list') signal, med_term, long_term = normalize_signals( [signal, med_term, long_term]) if p_bar is not None: p_bar.uptick(increment=0.1) triggers = ao_signal_trigger(signal, med_term, long_term) x = dates_extractor_list(position) name3 = INDEXES.get(name, name) name2 = name3 + ' - Awesome Oscillator' if plot_output: dual_plotting([signal, med_term, long_term], position['Close'], ['Awesome', 'Medium', 'Long'], 'Price') dual_plotting([signal, triggers], position['Close'], ['Awesome', 'Triggers'], 'Price') bar_chart(signal, position=position, x=x, title=name2, bar_delta=True) else: filename = os.path.join(name, view, f"awesome_bar_{name}") bar_chart(signal, position=position, x=x, saveFig=True, filename=filename, title=name2, bar_delta=True) if p_bar is not None: p_bar.uptick(increment=0.1) return signal
def rate_of_change_oscillator(fund: pd.DataFrame, periods: list = [10, 20, 40], **kwargs) -> dict: """Rate of Change Oscillator Arguments: fund {pd.DataFrame} -- fund dataset Keyword Arguments: periods {list} -- lookback periods for ROC (default: {[10, 20, 40]}) Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) views {str} -- (default: {''}) progress_bar {ProgressBar} -- (default: {None}) Returns: dict -- roc data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') views = kwargs.get('views', '') p_bar = kwargs.get('progress_bar') roc = dict() tshort = roc_signal(fund, periods[0]) if p_bar is not None: p_bar.uptick(increment=0.1) tmed = roc_signal(fund, periods[1]) if p_bar is not None: p_bar.uptick(increment=0.1) tlong = roc_signal(fund, periods[2]) if p_bar is not None: p_bar.uptick(increment=0.1) roc['tabular'] = {'short': tshort, 'medium': tmed, 'long': tlong} roc['short'] = periods[0] roc['medium'] = periods[1] roc['long'] = periods[2] tsx, ts2 = adjust_signals(fund, tshort, offset=periods[0]) tmx, tm2 = adjust_signals(fund, tmed, offset=periods[1]) tlx, tl2 = adjust_signals(fund, tlong, offset=periods[2]) if p_bar is not None: p_bar.uptick(increment=0.2) name2 = INDEXES.get(name, name) title = f"{name2} - Rate of Change Oscillator" plots = [ts2, tm2, tl2] xs = [tsx, tmx, tlx] if plot_output: dual_plotting(fund['Close'], [tshort, tmed, tlong], 'Price', 'Rate of Change', title=title, legend=[ f'ROC-{periods[0]}', f'ROC-{periods[1]}', f'ROC-{periods[2]}' ]) generic_plotting(plots, x=xs, title=title, legend=[ f'ROC-{periods[0]}', f'ROC-{periods[1]}', f'ROC-{periods[2]}' ]) else: filename = os.path.join(name, views, f"rate_of_change_{name}") dual_plotting(fund['Close'], [tshort, tmed, tlong], 'Price', 'Rate of Change', title=title, legend=[ f'ROC-{periods[0]}', f'ROC-{periods[1]}', f'ROC-{periods[2]}' ], saveFig=True, filename=filename) if p_bar is not None: p_bar.uptick(increment=0.1) roc = roc_metrics(fund, roc, plot_output=plot_output, name=name, views=views, p_bar=p_bar) roc['length_of_data'] = len(roc['tabular']['short']) roc['type'] = 'oscillator' if p_bar is not None: p_bar.uptick(increment=0.1) return roc
def momentum_metrics(position: pd.DataFrame, mo_dict: dict, **kwargs) -> dict: """Momentum Oscillator Metrics Combination of pure oscillator signal + weighted features Arguments: position {pd.DataFrame} -- fund mo_dict {dict} -- momentum oscillator dict Optional Args: plot_output {bool} -- plots in real time if True (default: {True}) name {str} -- name of fund (default: {''}) progress_bar {ProgressBar} -- (default: {None}) view {str} -- (default: {''}) Returns: dict -- mo_dict w/ updated keys and data """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') period_change = kwargs.get('period_change', 5) view = kwargs.get('view', '') weights = [1.3, 0.85, 0.55, 0.1] # Convert features to a "tabular" array tot_len = len(position['Close']) metrics = [0.0] * tot_len mo_features = mo_dict['signals'] signal = mo_dict['tabular'] for feat in mo_features: ind = feat['index'] if feat['type'] == 'bullish': val = 1.0 else: val = -1.0 metrics[ind] += val * weights[0] # Smooth the curves if ind - 1 >= 0: metrics[ind - 1] += val * weights[1] if ind + 1 < tot_len: metrics[ind + 1] += val * weights[1] if ind - 2 >= 0: metrics[ind - 2] += val * weights[2] if ind + 2 < tot_len: metrics[ind + 2] += val * weights[2] if ind - 3 >= 0: metrics[ind - 3] += val * weights[3] if ind + 3 < tot_len: metrics[ind + 3] += val * weights[3] norm_signal = normalize_signals([signal])[0] metrics4 = [] changes = [] for i, met in enumerate(metrics): metrics4.append(met + norm_signal[i]) min_ = np.abs(min(metrics4)) + 1.0 for _ in range(period_change): changes.append(0.0) for i in range(period_change, len(metrics4)): c = (((metrics4[i] + min_) / (metrics4[i - period_change] + min_)) - 1.0) * 100.0 changes.append(c) mo_dict['metrics'] = metrics4 mo_dict['changes'] = changes title = '(Chande) Momentum Oscillator Metrics' if plot_output: dual_plotting(position['Close'], metrics4, 'Price', 'Metrics', title=title) else: filename = os.path.join(name, view, f"momentum_metrics_{name}.png") dual_plotting(position['Close'], metrics4, 'Price', 'Metrics', title=title, saveFig=True, filename=filename) return mo_dict
def swing_trade_metrics(position: pd.DataFrame, swings: dict, **kwargs) -> dict: """Swing Trade Metrics Arguments: position {pd.DataFrame} -- fund dataset swings {dict} -- hull data object Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) p_bar {ProgressBar} -- (default: {None}) view {str} -- directory of plots (default: {''}) Returns: dict -- hull data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') p_bar = kwargs.get('p_bar') view = kwargs.get('view') weights = [1.0, 0.55, 0.25, 0.1] # Convert features to a "tabular" array tot_len = len(position['Close']) metrics = [0.0] * tot_len for i, val in enumerate(swings['tabular']['swing']): metrics[i] += val * weights[0] # Smooth the curves if i - 1 >= 0: metrics[i-1] += val * weights[1] if i + 1 < tot_len: metrics[i+1] += val * weights[1] if i - 2 >= 0: metrics[i-2] += val * weights[2] if i + 2 < tot_len: metrics[i+2] += val * weights[2] if i - 3 >= 0: metrics[i-3] += val * weights[3] if i + 3 < tot_len: metrics[i+3] += val * weights[3] norm_signal = normalize_signals([metrics])[0] metrics = simple_moving_avg(norm_signal, 7, data_type='list') swings['metrics'] = {'metrics': metrics} tshort = swings['tabular']['short'] tmed = swings['tabular']['medium'] tlong = swings['tabular']['long'] mshort = [] mmed = [] mlong = [] for i, close in enumerate(position['Close']): mshort.append(np.round((close - tshort[i]) / tshort[i] * 100.0, 3)) mmed.append(np.round((close - tmed[i]) / tmed[i] * 100.0, 3)) mlong.append(np.round((close - tlong[i]) / tlong[i] * 100.0, 3)) shp = swings['short']['period'] mdp = swings['medium']['period'] lgp = swings['long']['period'] swings['metrics'][f'{shp}-d'] = mshort swings['metrics'][f'{mdp}-d'] = mmed swings['metrics'][f'{lgp}-d'] = mlong name3 = INDEXES.get(name, name) name2 = name3 + ' - Hull Moving Average Metrics' if plot_output: dual_plotting(position['Close'], swings['metrics']['metrics'], 'Price', 'Metrics', title='Hull Moving Average Metrics') else: filename2 = os.path.join(name, view, f"hull_metrics_{name}.png") dual_plotting(position['Close'], swings['metrics']['metrics'], 'Price', 'Metrics', title=name2, saveFig=True, filename=filename2) if p_bar is not None: p_bar.uptick(increment=0.2) return swings
def rsi_divergence(position: pd.DataFrame, rsi_data: dict, **kwargs) -> dict: """RSI Divergence: 1. Cross outside threshold at Price A 2. Cross inside threshold 3. Cross outside threshold at Price B 4. Exit [inside] threshold, where Price B is more extreme than A Arguments: position {pd.DataFrame} -- dataset rsi_data {dict} -- rsi data object Optional Args: plot_output {bool} -- (default: {True}) p_bar {ProgressBar} -- (default: {None}) Returns: dict -- rsi data object """ plot_output = kwargs.get('plot_output', True) p_bar = kwargs.get('p_bar') signal = rsi_data['tabular'] ovb_th = rsi_data['thresholds']['overbought'] ovs_th = rsi_data['thresholds']['oversold'] divs = [0.0] * len(signal) state = 'n' maxima = 0.0 minima = 0.0 prices = [0.0, 0.0] rsi_vals = [0.0, 0.0] for i, sig in enumerate(signal): # Start with bullish divergence if (state == 'n') and (sig <= ovs_th[i]): state = 'u1' rsi_vals[0] = sig elif state == 'u1': if sig <= rsi_vals[0]: rsi_vals[0] = sig prices[0] = position['Close'][i] else: state = 'u2' maxima = sig elif state == 'u2': if sig >= maxima: if sig >= ovb_th[i]: state = 'e1' rsi_vals[0] = sig else: maxima = sig else: state = 'u3' rsi_vals[1] = sig elif state == 'u3': if sig <= rsi_vals[1]: prices[1] = position['Close'][i] rsi_vals[1] = sig else: if rsi_vals[1] <= ovs_th[i]: if (prices[1] < prices[0]) and (rsi_vals[0] < rsi_vals[1]): # Bullish divergence! divs[i] = 1.0 rsi_data['bullish'].append([ date_extractor(position.index[i], _format='str'), position['Close'][i], i, "divergence" ]) state = 'n' else: state = 'n' else: state = 'n' # Start with bearish divergence elif (state == 'n') and (sig >= ovb_th[i]): state = 'e1' rsi_vals[0] = sig elif state == 'e1': if sig >= rsi_vals[0]: rsi_vals[0] = sig prices[0] = position['Close'][i] else: state = 'e2' minima = sig elif state == 'e2': if sig <= minima: if sig <= ovs_th[i]: state = 'u1' rsi_vals[0] = sig else: minima = sig else: state = 'e3' rsi_vals[1] = sig elif state == 'e3': if sig >= rsi_vals[1]: prices[1] = position['Close'][i] rsi_vals[1] = sig else: if rsi_vals[1] >= ovb_th[i]: if (prices[1] > prices[0]) and (rsi_vals[0] > rsi_vals[1]): # Bearish divergence! divs[i] = -1.0 rsi_data['bearish'].append([ date_extractor(position.index[i], _format='str'), position['Close'][i], i, "divergence" ]) state = 'n' else: state = 'n' else: state = 'n' rsi_data['divergence'] = divs if plot_output: dual_plotting(position['Close'], divs, 'Price', 'RSI', title='Divs') if p_bar is not None: p_bar.uptick(increment=0.1) return rsi_data
def RSI(position: pd.DataFrame, **kwargs) -> dict: """Relative Strength Indicator Arguments: position {pd.DataFrame} -- list of y-value datasets to be plotted (multiple) Optional Args: name {str} -- name of fund, primarily for plotting (default: {''}) plot_output {bool} -- True to render plot in realtime (default: {True}) period {int} -- size of RSI indicator (default: {14}) out_suppress {bool} -- output plot/prints are suppressed (default: {True}) progress_bar {ProgressBar} -- (default: {None}) overbought {float} -- threshold to trigger overbought/sell condition (default: {70.0}) oversold {float} -- threshold to trigger oversold/buy condition (default: {30.0}) auto_trend {bool} -- True calculates basic trend, applies to thresholds (default: {True}) view {str} -- (default: {''}) trendlines {bool} -- (default: {False}) Returns: dict -- contains all rsi information """ name = kwargs.get('name', '') plot_output = kwargs.get('plot_output', True) period = kwargs.get('period', 14) out_suppress = kwargs.get('out_suppress', True) progress_bar = kwargs.get('progress_bar', None) overbought = kwargs.get('overbought', 70.0) oversold = kwargs.get('oversold', 30.0) auto_trend = kwargs.get('auto_trend', True) view = kwargs.get('view', '') trendlines = kwargs.get('trendlines', False) rsi_data = dict() rsi = generate_rsi_signal(position, period=period, p_bar=progress_bar) rsi_data['tabular'] = rsi slope_trend = [] if auto_trend: slope_trend = autotrend(position['Close'], periods=[period * 3, period * 5.5, period * 8], weights=[0.45, 0.33, 0.22], normalize=True) over_thresholds = over_threshold_lists(overbought, oversold, len(position['Close']), slope_list=slope_trend) if progress_bar is not None: progress_bar.uptick(increment=0.1) rsi_data['thresholds'] = over_thresholds # Determine indicators, swing rejection and divergences rsi_data = determine_rsi_swing_rejection(position, rsi_data, p_bar=progress_bar) rsi_data = rsi_divergence(position, rsi_data, plot_output=plot_output, p_bar=progress_bar) # Determine metrics, primarily using both indicators rsi_data = rsi_metrics(position, rsi_data, p_bar=progress_bar) main_plots = [ rsi, over_thresholds['overbought'], over_thresholds['oversold'] ] if not out_suppress: name3 = INDEXES.get(name, name) name2 = name3 + ' - RSI' if plot_output: dual_plotting(position['Close'], main_plots, 'Position Price', 'RSI', title=name2) dual_plotting(position['Close'], rsi_data['metrics'], 'Position Price', 'RSI Indicators', title=name2) else: filename1 = os.path.join(name, view, f"RSI_standard_{name}.png") filename2 = os.path.join(name, view, f"RSI_indicator_{name}.png") dual_plotting(position['Close'], main_plots, 'Position Price', 'RSI', title=name2, saveFig=True, filename=filename1) dual_plotting(position['Close'], rsi_data['metrics'], 'Position Price', 'RSI Metrics', title=name2, saveFig=True, filename=filename2) if progress_bar is not None: progress_bar.uptick(increment=0.1) if trendlines: end = len(rsi) rsi_trend = rsi[end - 100:end] get_trendlines_regression(rsi_trend, plot_output=True, indicator='RSI') rsi_data['type'] = 'oscillator' rsi_data['length_of_data'] = len(rsi) rsi_data['signals'] = rsi_signals(rsi_data['bullish'], rsi_data['bearish']) return rsi_data
def get_adx_signal(fund: pd.DataFrame, atr: list, **kwargs) -> dict: """Get ADX Signal Arguments: fund {pd.DataFrame} -- fund dataset atr {list} -- atr signal Optional Args: interval {int} -- lookback period (default: {14}) adx_default {float} -- default adx signal value before calculations appear (default: {20.0}) no_trend_value {float} -- threshold of ADX where signal is deemed to not have a trend (default: {20.0}) strong_trend_value {float} -- threshold of ADX where signal is deemed to have a strong trend (default: {25.0}) high_trend_value {float} -- threshold of ADX where signal drops below as trend weakens (default: {40.0}) plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- (default: {''}) pbar {ProgressBar} -- (default: {None}) Returns: dict -- tabular adx and DI signals """ interval = kwargs.get('interval', 14) ADX_DEFAULT = kwargs.get('adx_default', 20.0) NO_TREND = kwargs.get('no_trend_value', 20.0) STRONG_TREND = kwargs.get('strong_trend_value', 25.0) HIGH_TREND = kwargs.get('high_trend_value', 40.0) plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view', '') pbar = kwargs.get('pbar') signal = dict() # Calculate the directional movement signals dmp = [0.0] * len(fund['Close']) dmn = [0.0] * len(fund['Close']) for i in range(1, len(fund['Close'])): dmp[i] = fund['High'][i] - fund['High'][i - 1] dmn[i] = fund['Low'][i - 1] - fund['Low'][i] if dmp[i] > dmn[i]: dmn[i] = 0.0 else: dmp[i] = 0.0 if dmp[i] < 0.0: dmp[i] = 0.0 if dmn[i] < 0.0: dmn[i] = 0.0 if pbar is not None: pbar.uptick(increment=0.1) # Calculate the dm signals, di signals, dx signal with 'interval' averages dma_p = [0.0] * len(fund['Close']) dma_n = [0.0] * len(fund['Close']) di_p = [0.0] * len(fund['Close']) di_n = [0.0] * len(fund['Close']) dx_signal = [0.0] * len(fund['Close']) dma_p[interval - 1] = sum(dmp[0:interval]) dma_n[interval - 1] = sum(dmn[0:interval]) for i in range(interval, len(fund['Close'])): dma_p[i] = dma_p[i - 1] - (dma_p[i - 1] / float(interval)) + dmp[i] dma_n[i] = dma_n[i - 1] - (dma_n[i - 1] / float(interval)) + dmn[i] di_p[i] = dma_p[i] / atr[i] * 100.0 di_n[i] = dma_n[i] / atr[i] * 100.0 dx_signal[i] = abs(di_p[i] - di_n[i]) / (di_p[i] + di_n[i]) * 100.0 if pbar is not None: pbar.uptick(increment=0.3) # Finally, calculate the adx signal as an 'interval' average of dx adx_signal = [ADX_DEFAULT] * len(fund['Close']) adx_signal[interval - 1] = sum(dx_signal[0:interval]) / float(interval) for i in range(interval, len(adx_signal)): adx_signal[i] = ( (adx_signal[i - 1] * 13) + dx_signal[i]) / float(interval) if pbar is not None: pbar.uptick(increment=0.1) signal['di_+'] = di_p signal['di_-'] = di_n signal['adx'] = adx_signal signal['high_trend'] = [HIGH_TREND] * len(adx_signal) signal['no_trend'] = [NO_TREND] * len(adx_signal) signal['strong_trend'] = [STRONG_TREND] * len(adx_signal) plots = [ signal['no_trend'], signal['high_trend'], signal['strong_trend'], signal['adx'] ] name2 = INDEXES.get(name, name) title = f"{name2} - Average Directional Index (ADX)" if plot_output: dual_plotting(fund['Close'], plots, 'Price', ['No Trend', 'Over Trend', 'Strong Trend', 'ADX'], title=title) else: filename = os.path.join(name, view, f"adx_tabular_{name}.png") dual_plotting(fund['Close'], plots, 'Price', ['No Trend', 'Over Trend', 'Strong Trend', 'ADX'], title=title, saveFig=True, filename=filename) if pbar is not None: pbar.uptick(increment=0.1) return signal
def composite_index(data: dict, sectors: list, progress_bar=None, plot_output=True) -> list: """Composite Index Arguments: data {dict} -- data sectors {list} -- list of sectors Keyword Arguments: progress_bar {ProgressBar} -- (default: {None}) plot_output {bool} -- (default: {True}) Returns: list -- correlation vector """ composite = [] for tick in sectors: cluster = cluster_oscs(data[tick], plot_output=False, function='market', wma=False, progress_bar=progress_bar) graph = cluster['tabular'] composite.append(graph) composite2 = [] for i in range(len(composite[0])): s = 0.0 for j in range(len(composite)): s += float(composite[j][i]) composite2.append(s) progress_bar.uptick() composite2 = windowed_moving_avg(composite2, 3, data_type='list') max_ = np.max(np.abs(composite2)) composite2 = [x / max_ for x in composite2] progress_bar.uptick() if plot_output: dual_plotting(data['^GSPC']['Close'], composite2, y1_label='S&P500', y2_label='MCI', title='Market Composite Index') else: dual_plotting(data['^GSPC']['Close'], composite2, y1_label='S&P500', y2_label='MCI', title='Market Composite Index', saveFig=True, filename='MCI.png') progress_bar.uptick() return composite2
def assemble_last_signals(meta_sub: dict, fund: pd.DataFrame = None, lookback: int = 10, **kwargs) -> dict: """assemble_last signals Look through all indicators of lookback time and list them Arguments: meta_sub {dict} -- metadata subset "metadata[fund][view]" Keyword Arguments: lookback {int} -- number of trading periods into past to find signals (default: {5}) fund {pd.DataFrame} -- fund dataset Optional Args: standalone {bool} -- if run as a function, fetch all metadata info (default: {False}) print_out {bool} -- print in terminal (default: {False}) name {str} -- (default: {''}) pbar {ProgressBar} -- (default: {None}) Returns: dict -- last signals data object """ standalone = kwargs.get('standalone', False) print_out = kwargs.get('print_out', False) name = kwargs.get('name', '') pbar = kwargs.get('progress_bar') plot_output = kwargs.get('plot_output', True) metadata = [] meta_keys = [] name2 = INDEXES.get(name, name) if fund is not None: fund = fund['Close'] if standalone: for key in meta_sub: if key not in EXEMPT_METRICS: metadata.append(meta_sub[key]) meta_keys.append(key) else: metadata = [meta_sub] meta_keys.append('') increment = 1.0 / float(len(metadata)) content = {"signals": [], "metrics": []} for a, sub in enumerate(metadata): content['signals'] = [] for key in sub: if key in INDICATOR_NAMES: if SIGNAL_KEY in sub[key] and SIZE_KEY in sub[key]: start_period = sub[key][SIZE_KEY] - lookback - 1 for signal in sub[key][SIGNAL_KEY]: if signal['index'] >= start_period: data = { "type": signal['type'], "indicator": key, "value": signal['value'], "date": signal['date'], "days_ago": (sub[key][SIZE_KEY] - 1 - signal['index']) } content["signals"].append(data) if METRICS_KEY in sub[key] and TYPE_KEY in sub[key]: if sub[key][TYPE_KEY] == 'oscillator': if len(content["metrics"]) == 0: content["metrics"] = sub[key][METRICS_KEY] else: for i, met in enumerate(sub[key][METRICS_KEY]): content["metrics"][i] += met else: for trend in sub[key][METRICS_KEY]: if trend != 'metrics': if len(content["metrics"]) == 0: content["metrics"] = [ x / 10.0 for x in sub[key][METRICS_KEY][trend] ] else: for i, met in enumerate( sub[key][METRICS_KEY][trend]): content["metrics"][i] += met / 10.0 content["signals"].sort(key=lambda x: x['days_ago']) if pbar is not None: pbar.uptick(increment=increment) if fund is None: fund = sub.get(STATS_KEY, {}).get('tabular') if print_out: content_printer(content, meta_keys[a], name=name2) if fund is not None: title = f"NATA Metrics - {name2}" upper = 0.3 * max(content["metrics"]) upper = [upper] * len(content["metrics"]) lower = 0.3 * min(content["metrics"]) lower = [lower] * len(content["metrics"]) if plot_output: dual_plotting(fund, [content["metrics"], upper, lower], 'Price', 'Metrics', title=title) else: filename = os.path.join(name, meta_keys[a], f"overall_metrics_{name}.png") dual_plotting(fund, [content["metrics"], upper, lower], 'Price', 'Metrics', title=title, saveFig=True, filename=filename) return content
def composite_correlation(data: dict, sectors: list, progress_bar=None, plot_output=True) -> dict: """Composite Correlation Betas and r-squared for 2 time periods for each sector (full, 1/2 time); plot of r-squared vs. S&P500 for last 50 or 100 days for each sector. Arguments: data {dict} -- data object sectors {list} -- list of sectors Keyword Arguments: progress_bar {ProgressBar} -- (default: {None}) plot_output {bool} -- (default: {True}) Returns: dict -- correlation dictionary """ DIVISOR = 5 correlations = {} if '^GSPC' in data.keys(): tot_len = len(data['^GSPC']['Close']) start_pt = int(np.floor(tot_len / DIVISOR)) if start_pt > 100: start_pt = 100 corrs = {} dates = data['^GSPC'].index[start_pt:tot_len] net_correlation = [0.0] * (tot_len - start_pt) DIVISOR = 10.0 increment = float(len(sectors)) / (float(tot_len - start_pt) / DIVISOR * float(len(sectors))) counter = 0 for sector in sectors: correlations[sector] = simple_beta_rsq( data[sector], data['^GSPC'], recent_period=[int(np.round(tot_len / 2, 0)), tot_len]) corrs[sector] = [] for i in range(start_pt, tot_len): _, rsqd = beta_comparison_list( data[sector]['Close'][i - start_pt:i], data['^GSPC']['Close'][i - start_pt:i]) corrs[sector].append(rsqd) net_correlation[i - start_pt] += rsqd counter += 1 if counter == DIVISOR: progress_bar.uptick(increment=increment) counter = 0 plots = [corrs[x] for x in corrs.keys()] legend = [x for x in corrs.keys()] generic_plotting(plots, x=dates, title='MCI Correlations', legend=legend, saveFig=(not plot_output), filename='MCI_correlations.png') progress_bar.uptick() max_ = np.max(net_correlation) net_correlation = [x / max_ for x in net_correlation] legend = ['Net Correlation', 'S&P500'] dual_plotting(net_correlation, data['^GSPC']['Close'][start_pt:tot_len], x=dates, y1_label=legend[0], y2_label=legend[1], title='MCI Net Correlation', saveFig=(not plot_output), filename='MCI_net_correlation.png') progress_bar.uptick() return correlations
def generate_obv_content(fund: pd.DataFrame, **kwargs) -> dict: """Generate On Balance Signal Content Arguments: fund {pd.DataFrame} Optional Args: plot_output {bool} -- (default: {True}) filter_factor {float} -- threshold divisor (x/filter_factor) for "significant" OBVs (default: {2.5}) sma_interval {int} -- interval for simple moving average (default: {20}) name {str} -- (default: {''}) progress_bar {ProgressBar} -- (default: {None}) view {'str'} -- period (default: {None}) Returns: dict -- obv data object """ plot_output = kwargs.get('plot_output', True) filter_factor = kwargs.get('filter_factor', 2.5) sma_interval = kwargs.get('sma_interval', 20) name = kwargs.get('name', '') progress_bar = kwargs.get('progress_bar') view = kwargs.get('view') obv_dict = dict() obv = generate_obv_signal(fund) obv_dict['obv'] = obv if progress_bar is not None: progress_bar.uptick(increment=0.125) ofilter, features = obv_feature_detection(obv, fund, sma_interval=sma_interval, filter_factor=filter_factor, progress_bar=progress_bar, plot_output=plot_output) obv_dict['tabular'] = ofilter obv_dict['signals'] = features if progress_bar is not None: progress_bar.uptick(increment=0.125) volume = [] volume.append(fund['Volume'][0]) for i in range(1, len(fund['Volume'])): if fund['Close'][i] - fund['Close'][i - 1] < 0: volume.append(-1.0 * fund['Volume'][i]) else: volume.append(fund['Volume'][i]) x = dates_extractor_list(fund) name3 = INDEXES.get(name, name) name2 = name3 + ' - On Balance Volume (OBV)' name4 = name3 + ' - Significant OBV Changes' name5 = name3 + ' - Volume' if plot_output: dual_plotting(fund['Close'], obv, x=x, y1_label='Position Price', y2_label='On Balance Volume', x_label='Trading Days', title=name2, subplot=True) dual_plotting(fund['Close'], ofilter, x=x, y1_label='Position Price', y2_label='OBV-DIFF', x_label='Trading Days', title=name4, subplot=True) bar_chart(volume, x=x, position=fund, title=name5, all_positive=True) else: filename = os.path.join(name, view, f"obv_diff_{name}.png") filename2 = os.path.join(name, view, f"obv_standard_{name}.png") filename3 = os.path.join(name, view, f"volume_{name}.png") bar_chart(volume, x=x, position=fund, title=name5, saveFig=True, filename=filename3, all_positive=True) bar_chart(ofilter, x=x, position=fund, title=name4, saveFig=True, filename=filename) dual_plotting(fund['Close'], obv, x=x, y1_label='Position Price', y2_label='On Balance Volume', x_label='Trading Days', title=name2, saveFig=True, filename=filename2) if progress_bar is not None: progress_bar.uptick(increment=0.125) obv_dict['length_of_data'] = len(obv_dict['tabular']) return obv_dict
def awesome_metrics(position: pd.DataFrame, ao_dict: dict, **kwargs) -> dict: """Awesome Oscillator Metrics Combination of pure oscillator signal + weighted trigger signals Arguments: position {pd.DataFrame} -- fund ao_dict {dict} -- awesome oscillator dictionary Optional Args: plot_output {bool} -- plots in real time if True (default: {True}) name {str} -- name of fund (default: {''}) progress_bar {ProgressBar} -- (default: {None}) Returns: dict -- ao_dict w/ updated keys and data """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') p_bar = kwargs.get('progress_bar') period_change = kwargs.get('period_change', 5) view = kwargs.get('view') weights = [1.0, 0.85, 0.5, 0.05] # Convert features to a "tabular" array tot_len = len(position['Close']) metrics = [0.0] * tot_len plus_minus = ao_dict.get('pm', {}).get('pm_data') indexes = ao_dict.get('pm', {}).get('indexes', []) for i, ind in enumerate(indexes): metrics[ind] += float(plus_minus[i]) # Smooth the curves if ind - 1 >= 0: metrics[ind - 1] += float(plus_minus[i]) * weights[1] if ind + 1 < tot_len: metrics[ind + 1] += float(plus_minus[i]) * weights[1] if ind - 2 >= 0: metrics[ind - 2] += float(plus_minus[i]) * weights[2] if ind + 2 < tot_len: metrics[ind + 2] += float(plus_minus[i]) * weights[2] if ind - 3 >= 0: metrics[ind - 3] += float(plus_minus[i]) * weights[3] if ind + 3 < tot_len: metrics[ind + 3] += float(plus_minus[i]) * weights[3] metrics2 = exponential_moving_avg(metrics, 7, data_type='list') norm = normalize_signals([metrics2]) metrics3 = norm[0] if p_bar is not None: p_bar.uptick(increment=0.1) ao_signal = ao_dict['tabular'] metrics4 = [] for i, met in enumerate(metrics3): metrics4.append(met + ao_signal[i]) changes = [] min_ = 1.0 - min(metrics4) for _ in range(period_change): changes.append(0.0) for i in range(period_change, len(metrics4)): c = (((metrics4[i] + min_) / (metrics4[i - period_change] + min_)) - min_) * 100.0 changes.append(c) ao_dict['metrics'] = metrics4 ao_dict['changes'] = changes title = 'Awesome Oscillator Metrics' if plot_output: dual_plotting(position['Close'], metrics4, 'Price', 'Metrics', title=title) else: filename = os.path.join(name, view, f"awesome_metrics_{name}.png") dual_plotting(position['Close'], metrics4, 'Price', 'Metrics', title=title, saveFig=True, filename=filename) ao_dict.pop('pm') return ao_dict
def roc_metrics(fund: pd.DataFrame, roc_dict: dict, **kwargs) -> dict: """Rate of Change Metrics Arguments: fund {pd.DataFrame} -- fund dataset roc_dict {dict} -- roc data object Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) views {str} -- (default: {''}) p_bar {ProgressBar} -- (default: {None}) Returns: dict -- roc data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') views = kwargs.get('views', '') p_bar = kwargs.get('p_bar') MAP_TABULAR = {'short': 0.65, 'medium': 1.0, 'long': 0.4} roc_dict['metrics'] = [0.0] * len(fund['Close']) roc_dict['signals'] = [] # Look at zero crossovers first roc_dict = roc_zero_crossovers(fund, roc_dict, MAP_TABULAR) if p_bar is not None: p_bar.uptick(increment=0.1) roc_dict = roc_over_threshold_crossovers(fund, roc_dict, MAP_TABULAR) if p_bar is not None: p_bar.uptick(increment=0.1) roc_dict['metrics'] = exponential_moving_avg(roc_dict['metrics'], 7, data_type='list') roc_dict['metrics'] = normalize_signals([roc_dict['metrics']])[0] if p_bar is not None: p_bar.uptick(increment=0.1) name2 = INDEXES.get(name, name) title = f"{name2} - Rate of Change Metrics" if plot_output: dual_plotting(fund['Close'], roc_dict['metrics'], 'Price', 'ROC Metrics', title=title) else: filename = os.path.join(name, views, f"roc_metrics_{name}") dual_plotting(fund['Close'], roc_dict['metrics'], 'Price', 'ROC Metrics', title=title, saveFig=True, filename=filename) return roc_dict
def composite_index(data: dict, sectors: list, index_dict: dict, plot_output=True, bond_type='Treasury', index_type='BND', **kwargs): """Composite Index Arguments: data {dict} -- Data of composites sectors {list} -- sector list index_dict {dict} -- data pulled from sectors.json file (can be None) Keyword Arguments: plot_output {bool} -- (default: {True}) bond_type {str} -- (default: {'Treasury'}) index_type {str} -- (default: {'BND'}) Optional Args: clock {uint64_t} -- timekeeping for prog_bar (default: {None}) Returns: list -- composite signal, plots, dates """ clock = kwargs.get('clock') progress = len(sectors) + 2 p = ProgressBar( progress, name=f'{bond_type} Bond Composite Index', offset=clock) p.start() composite = [] for tick in sectors: if tick != index_type: cluster = cluster_oscs( data[tick], plot_output=False, function='market', wma=False) graph = cluster['tabular'] p.uptick() composite.append(graph) composite2 = [] for i in range(len(composite[0])): s = 0.0 for j in range(len(composite)): s += float(composite[j][i]) composite2.append(s) p.uptick() composite2 = windowed_moving_avg(composite2, 3, data_type='list') max_ = np.max(np.abs(composite2)) composite2 = [x / max_ for x in composite2] key = list(data.keys())[0] data_to_plot = bond_type_index_generator( data, index_dict, bond_type=bond_type) dates = dates_extractor_list(data[key]) if plot_output: dual_plotting(data_to_plot, composite2, y1_label=index_type, y2_label='BCI', title=f'{bond_type} Bond Composite Index') else: dual_plotting(data_to_plot, composite2, y1_label=index_type, y2_label='BCI', title=f'{bond_type} Bond Composite Index', x=dates, saveFig=True, filename=f'{bond_type}_BCI.png') p.uptick() return composite2, data_to_plot, dates
def get_correlation(data: dict, sectors: list, **kwargs) -> dict: """Get Correlation Arguments: data {dict} -- downloaded data sectors {list} -- sector list Optional Arguments: plot_output {bool} -- (default: {True}) clock {uint64_t} -- time for prog_bar (default: {None}) Returns: dict -- object with correlations """ plot_output = kwargs.get('plot_output', True) clock = kwargs.get('clock') PERIOD_LENGTH = [100, 50, 25] corr_data = dict() if '^GSPC' in data.keys(): tot_len = len(data['^GSPC']['Close']) start_pt = max(PERIOD_LENGTH) tot_count = (tot_len - start_pt) * 11 * len(PERIOD_LENGTH) if tot_count < 25000: divisor = 50 else: divisor = 250 pbar_count = np.ceil(tot_count / float(divisor)) progress_bar = ProgressBar(pbar_count, name="Correlation Composite Index", offset=clock) progress_bar.start() corrs = {} dates = data['^GSPC'].index[start_pt:tot_len] net_correlation = [] legend = [] counter = 0 for period in PERIOD_LENGTH: nc = [0.0] * (tot_len - start_pt) for sector in sectors: corrs[sector] = [] for i in range(start_pt, tot_len): _, rsqd = beta_comparison_list( data[sector]['Close'][i - period:i], data['^GSPC']['Close'][i - period:i]) corrs[sector].append(rsqd) nc[i - start_pt] += rsqd counter += 1 if counter == divisor: progress_bar.uptick() counter = 0 net_correlation.append(nc.copy()) legend.append('Corr-' + str(period)) norm_corr = [] for nc_period in net_correlation: max_ = np.max(nc_period) norm = [x / max_ for x in nc_period] norm_corr.append(norm) net_correlation = norm_corr.copy() dual_plotting(net_correlation, data['^GSPC']['Close'][start_pt:tot_len], x=dates, y1_label=legend, y2_label='S&P500', title='CCI Net Correlation', saveFig=(not plot_output), filename='CCI_net_correlation.png') str_dates = [] for date in dates: str_dates.append(date.strftime("%Y-%m-%d")) corr_data['tabular'] = {} for i, nc_period in enumerate(net_correlation): corr_data[legend[i]] = {} corr_data[legend[i]]['data'] = nc_period.copy() corr_data[legend[i]]['date'] = str_dates.copy() corr_data['tabular'][legend[i]] = nc_period.copy() corr_data['tabular']['date'] = str_dates.copy() progress_bar.end() return corr_data
def generate_di_signal(fund: pd.DataFrame, **kwargs) -> list: """Generate Demand Index Signal Arguments: fund {pd.DataFrame} -- fund dataset Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- (default: {''}) progress_bar {ProgressBar} -- (default: {None}) Returns: list -- demand index signal """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view', '') pbar = kwargs.get('pbar') signal = [] # First, generate the "2d H-L volatility" signal. vol_hl = [0.0] * len(fund['Close']) for i in range(1, len(vol_hl)): high = max([fund['High'][i], fund['High'][i-1]]) low = min([fund['Low'][i], fund['Low'][i-1]]) vol_hl[i] = high - low if pbar is not None: pbar.uptick(increment=0.05) vol_av = simple_moving_avg(vol_hl, 10, data_type='list') if pbar is not None: pbar.uptick(increment=0.05) # Calculate the constant 'K'. const_K = [0.0] * len(vol_av) for i, vol in enumerate(vol_av): if vol == 0.0: const_K[i] = 0.0 else: const_K[i] = 3.0 * fund['Close'][i] / vol if pbar is not None: pbar.uptick(increment=0.05) # Calculate daily percent change of open-close. percent = [0.0] * len(vol_av) for i, ope in enumerate(fund['Open']): percent[i] = (fund['Close'][i] - ope) / ope * const_K[i] if pbar is not None: pbar.uptick(increment=0.05) # Calculate BP and SP, so we can get DI = BP/SP | SP/BP B_P = [] S_P = [] for i, vol in enumerate(fund['Volume']): if percent[i] == 0.0: B_P.append(0.0) S_P.append(0.0) elif percent[i] > 0.0: B_P.append(vol) S_P.append(vol / percent[i]) else: B_P.append(vol / percent[i]) S_P.append(vol) if pbar is not None: pbar.uptick(increment=0.05) for i, bpx in enumerate(B_P): if abs(bpx) > abs(S_P[i]): signal.append(S_P[i] / bpx) else: if abs(bpx) == 0.0 and abs(S_P[i]) == 0.0: signal.append(0.0) else: signal.append(bpx / S_P[i]) if pbar is not None: pbar.uptick(increment=0.1) signal = exponential_moving_avg(signal, 10, data_type='list') if pbar is not None: pbar.uptick(increment=0.05) name2 = INDEXES.get(name, name) title = f"{name2} - Demand Index" if plot_output: dual_plotting(fund['Close'], signal, 'Price', 'Demand Index', title=title) else: filename = os.path.join(name, view, f"demand_index_{name}") dual_plotting(fund['Close'], signal, 'Price', 'Demand Index', title=title, saveFig=True, filename=filename) if pbar is not None: pbar.uptick(increment=0.1) return signal
def bollinger_metrics(position: pd.DataFrame, bol_bands: dict, **kwargs) -> dict: """Bollinger Metrics Arguments: position {pd.DataFrame} -- dataset of fund bol_bands {dict} -- bollinger band data object Optional Args: period {int} -- look back period for Stdev (default: {20}) plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- (default: {''}) Returns: dict -- bollinger band data object """ period = kwargs.get('period', 20) plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view') weights = [1.0, 0.6, 0.25, 0.0] tot_len = len(position['Close']) metrics = [0.0] * tot_len for feat in bol_bands['indicators']: ind = feat['index'] if feat['type'] == 'bullish': val = 1.0 else: val = -1.0 metrics[ind] += val * weights[0] # Smooth the curves if ind - 1 >= 0: metrics[ind - 1] += val * weights[1] if ind + 1 < tot_len: metrics[ind + 1] += val * weights[1] if ind - 2 >= 0: metrics[ind - 2] += val * weights[2] if ind + 2 < tot_len: metrics[ind + 2] += val * weights[2] if ind - 3 >= 0: metrics[ind - 3] += val * weights[3] if ind + 3 < tot_len: metrics[ind + 3] += val * weights[3] close = position['Close'] bb = bol_bands['tabular'] for i in range(period, len(close)): s_range = bb['middle_band'][i] - bb['lower_band'][i] if metrics[i] <= 0.6 and metrics[i] >= -0.6: c = close[i] - bb['middle_band'][i] c = np.round(c / s_range, 4) metrics[i] = c metrics = exponential_moving_avg(metrics, 7, data_type='list') norm_signal = normalize_signals([metrics])[0] bol_bands['metrics'] = norm_signal name3 = INDEXES.get(name, name) name2 = name3 + f" - Bollinger Band Metrics" if plot_output: dual_plotting(position['Close'], norm_signal, 'Price', 'Indicators', title=name2) else: filename = os.path.join(name, view, f"bollinger_band_metrics_{name}.png") # filename = name + f"/{view}" + f"/bollinger_band_metrics_{name}.png" dual_plotting(position['Close'], norm_signal, 'Price', 'Metrics', title=name2, saveFig=True, filename=filename) return bol_bands
def demand_index_metrics(fund: pd.DataFrame, dmx: dict, **kwargs) -> list: """Demand Index Metrics Arguments: fund {pd.DataFrame} -- fund dataset dmx {dict} -- demand index data object Optional Args: plot_output {bool} -- (default: {True}) name {str} -- (default: {''}) view {str} -- (default: {''}) progress_bar {ProgressBar} -- (default: {None}) Returns: list -- demand index data object """ plot_output = kwargs.get('plot_output', True) name = kwargs.get('name', '') view = kwargs.get('view', '') pbar = kwargs.get('pbar') metrics = [0.0] * len(dmx['tabular']) weight_map = { "zero crossover": [1.2, 0.95, 0.6, 0.1], "dual-signal-line": [1.0, 0.85, 0.55, 0.1], "signal-line zone": [0.8, 0.65, 0.4, 0.1] } for sig in dmx['signals']: ind = sig['index'] s = 1.0 if sig['type'] == 'bearish': s = -1.0 if 'dual-signal-line' in sig['value']: weights = weight_map['dual-signal-line'] elif 'signal-line zone' in sig['value']: weights = weight_map['signal-line zone'] else: weights = weight_map['zero crossover'] metrics[ind] = s # Smooth the curves if ind - 1 >= 0: metrics[ind-1] += s * weights[1] if ind + 1 < len(metrics): metrics[ind+1] += s * weights[1] if ind - 2 >= 0: metrics[ind-2] += s * weights[2] if ind + 2 < len(metrics): metrics[ind+2] += s * weights[2] if ind - 3 >= 0: metrics[ind-3] += s * weights[3] if ind + 3 < len(metrics): metrics[ind+3] += s * weights[3] if pbar is not None: pbar.uptick(increment=0.1) metrics = exponential_moving_avg(metrics, 7, data_type='list') norm = normalize_signals([metrics]) metrics = norm[0] if pbar is not None: pbar.uptick(increment=0.1) name2 = INDEXES.get(name, name) title = f"{name2} - Demand Index" if plot_output: dual_plotting(fund['Close'], metrics, 'Price', 'Demand Index Metrics') else: filename = os.path.join(name, view, f"di_metrics_{name}") dual_plotting(fund['Close'], metrics, 'Price', 'Demand Index Metrics', title=title, saveFig=True, filename=filename) return metrics