From 8907c286e857b622af171c2d8ac9040b05970549 Mon Sep 17 00:00:00 2001 From: loit Date: Mon, 28 Jul 2025 18:00:08 -0400 Subject: add intersection interpolation code for graphing EMAs visually --- analysis.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app.py | 25 +++++++++++++--- main.py | 10 ++++--- 3 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 analysis.py diff --git a/analysis.py b/analysis.py new file mode 100644 index 0000000..2b5ed59 --- /dev/null +++ b/analysis.py @@ -0,0 +1,94 @@ +import json +import datetime + +# pull stock data from json files +# timestamps_file = open('timestamps.json', 'r') +# timestamps_file_data = timestamps_file.read() +# timestamps = json.loads(timestamps_file_data) +# timestamps = [datetime.datetime.fromtimestamp(t) for t in timestamps] + +# prices_file = open('close_prices.json', 'r') +# prices = json.loads(prices_file.read()) + +# print('timestamps:\t', timestamps, '\nprices:\t', prices) + +# make the line data for the 5 day exponential moving average (EMA) + +def calc_first_sma(period, prices): + prices_sum = 0 + for i in range(0, period): + prices_sum += prices[i] # 0, 1, 2, 3 ("popping" order) + # print('prices_sum:\t', prices_sum) + + return prices_sum / period + +def calc_emas(period, prices): + weighted_multiplier = 2.0 / (period + 1.0) + + # calculate the first ema + first_ema = calc_first_sma(period, prices) + + # calculate the rest ema's using that first + emas = [first_ema] * period + for i in range(period + 1, len(prices)): # 4, 5, 6, ... , last + last_ema = emas[-1] + next_ema = prices[i] * weighted_multiplier + last_ema * (1 - weighted_multiplier) + emas.append(next_ema) + return emas + +def interpolate_intersection(intersection_indices, timestamps, prices1, prices2): + left_index = intersection_indices[0] + right_index = intersection_indices[1] + if right_index == -1: + return timestamps[left_index] + + y_1 = prices1[left_index] + y_2 = prices1[right_index] # first line + + v_1 = prices2[left_index] + v_2 = prices2[right_index] # second line + + x_1 = 0 # take this as zero the simplify the algebra + x_diff = timestamps[right_index] - timestamps[left_index] # same for both lines + + # find intersection between those lines + x_diff = x_diff.total_seconds() + m_1 = (y_2 - y_1) / x_diff # slope of line 1 + m_2 = (v_2 - v_1) / x_diff + + x_interpolated = (v_1 - y_1) / (m_1 - m_2) + y_interpolated = m_1 * (x_interpolated) + y_1 + + # add back the time we subtracted to make x_1=0 + x_interpolated = datetime.timedelta(seconds = x_interpolated) + timestamps[left_index] + return (x_interpolated, y_interpolated) + + + +""" +Returns the indices of where two arrays' values intersects +""" +def find_intersections(prices1, prices2): + if len(prices1) != len(prices2): + print("ERROR IN find_intersections: len of arrs not the same") + return [] + prev_p1 = prices1[0] + prev_p2 = prices2[0] + intersection_indices = set() + for i in range(1, len(prices1)): + next_p1 = prices1[i] + next_p2 = prices2[i] + # if the sign (negative to positive) changes, then there was an intersection between these pts + sub_prev = prev_p1 - prev_p2 + sub_next = next_p1 - next_p2 + + if (sub_prev > 0 and sub_next < 0) or (sub_prev < 0 and sub_next > 0): # TODO, consider on the 0 case + intersection_indices.add((i-1, i)) + + if sub_next == 0: + intersection_indices.add((i, -1)) + + prev_p1 = next_p1 + prev_p2 = next_p2 + + return intersection_indices diff --git a/app.py b/app.py index a2e9f07..5cd5521 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,5 @@ from dash import Dash, dcc, html, Input, Output +from analysis import calc_emas, find_intersections, interpolate_intersection import plotly.graph_objects as go import json import datetime @@ -21,13 +22,23 @@ app.layout = html.Div([ # pull stock data from json files timestamps_file = open('timestamps.json', 'r') timestamps_file_data = timestamps_file.read() -timestamps = json.loads(timestamps_file_data) -timestamps = [datetime.datetime.fromtimestamp(t) for t in timestamps] +timestamps_raw = json.loads(timestamps_file_data) +timestamps = [datetime.datetime.fromtimestamp(t) for t in timestamps_raw] prices_file = open('close_prices.json', 'r') prices = json.loads(prices_file.read()) -# print('timestamps:\t', timestamps, '\nprices:\t', prices) +ema_5 = calc_emas(5, prices) +ema_13 = calc_emas(13, prices) + +intersection_indices = find_intersections(ema_5, ema_13) +interpolated_intersections = [interpolate_intersection(indices, timestamps, ema_5, ema_13) for indices in intersection_indices] +intersected_x = [] +intersected_y = [] +for x,y in interpolated_intersections: + intersected_x.append(x) + intersected_y.append(y) + @app.callback( @@ -35,7 +46,13 @@ prices = json.loads(prices_file.read()) Input("dropdown", "value")) def display_color(color): fig = go.Figure( - data=go.Line(x=timestamps, y=prices, marker_color=color)) + [ + go.Scatter(name='Price', x=timestamps, y=prices, line=dict(color='rgb(0, 255, 255)'), mode='lines'), # prices + go.Scatter(name='5 day EMA', x=timestamps, y=ema_5, line=dict(color='rgb(0, 255, 0)'), mode='lines'), # 5 ema line + go.Scatter(name='13 day EMA', x=timestamps, y=ema_13, line=dict(color='rgb(0, 0, 255)'), mode='lines'), # 13 ema line + go.Scatter(name='EMA Intersections', x=intersected_x, y=intersected_y, line=dict(color='rgb(255, 0, 0)'), mode='markers') # EMA intersection points + ] + ) return fig diff --git a/main.py b/main.py index 8e7afdb..2a4ddff 100644 --- a/main.py +++ b/main.py @@ -1,14 +1,16 @@ import requests import json +import datetime print("hello") """ First pull data from yahoo api """ -params = {'period1' : '1753487940', - 'period2' : '1753725600', - 'interval' : '1m', +params = { #'period1' : '1753487940', + #'period2' : '1753725600', + 'interval' : '1d', + 'range' : '1y', 'events' : 'div|split|earn', 'includePrePost' : 'false' } headers = {'User-agent' : 'fin-backtesting-proj'} @@ -38,5 +40,5 @@ timestamps_file.close() close_prices_file = open('close_prices.json', 'w') close_prices_file.truncate(0) close_prices_file.write(close_prices_encoded) -timestamps_file.close() +close_prices_file.close() -- cgit v1.2.3-70-g09d2