diff options
-rw-r--r-- | analysis.py | 59 | ||||
-rw-r--r-- | api.py | 38 | ||||
-rw-r--r-- | app.py | 134 | ||||
-rw-r--r-- | ema.py | 63 | ||||
-rw-r--r-- | main.py | 17 |
5 files changed, 196 insertions, 115 deletions
diff --git a/analysis.py b/analysis.py index 644fe93..15ba0ef 100644 --- a/analysis.py +++ b/analysis.py @@ -14,27 +14,7 @@ import datetime # 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] @@ -72,8 +52,8 @@ def find_intersections(prices1, prices2, offset=0): 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] + prev_p1 = prices1[offset] + prev_p2 = prices2[offset] intersection_indices = set() for i in range(1 + offset, len(prices1)): next_p1 = prices1[i] @@ -93,39 +73,4 @@ def find_intersections(prices1, prices2, offset=0): return intersection_indices -def calculate_profit(buy_line, sell_line, prices, timestamps, offset=0, starting_money=10000): - if len(buy_line) != len(sell_line): - print("ERROR IN find_intersections: len of arrs not the same") - return [] - is_bought = False - curr_money = 10000 - shares_owned = 0 - buy_info = [] - sell_info = [] - for i in range(offset, len(buy_line)): - current_b1 = buy_line[i] - current_sl = sell_line[i] - # if the sign is positive, we want to hold, if it's negative, we want to sell - sign_signal = current_b1 - current_sl - - if sign_signal > 0: - if not is_bought: - # buy the stock - shares_owned = curr_money / prices[i] - curr_money = 0 - buy_info.append((timestamps[i], prices[i], i)) - is_bought = True - if sign_signal < 0: - if is_bought: - # selling the stock - curr_money = prices[i] * shares_owned - shares_owned = 0 - sell_info.append((timestamps[i], prices[i], i)) - is_bought = False - - # TODO: consider end interval - total_assets = prices[-1] * shares_owned + curr_money - percent_gain = (total_assets - starting_money) / starting_money - return (percent_gain, total_assets, buy_info, sell_info) -
\ No newline at end of file @@ -0,0 +1,38 @@ +import requests +import json + +""" +Given the parameters, +fetches the data for the corresponding chart using yahoo finance. +Expect it to raise an error on bad params! +""" +def fetch_chart_data(ticker, period='1y', interval='1d'): + params = { + 'interval' : interval, # 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 4h, 1d, 5d, 1wk, 1mo, 3mo + 'range' : period, # "1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max" + 'events' : 'div|split|earn', + 'includePrePost' : 'false' } + headers = {'User-agent' : 'fin-backtesting-proj'} + r = requests.get("https://query2.finance.yahoo.com/v8/finance/chart/" + ticker, headers=headers, params=params) + + print(r.url) + print("status_code:\t", r.status_code) + + # decode the JSON response data into a Python object + r.raise_for_status() # raises if error before parsing + data_obj = r.json() + + # get the specific data we want + timestamps = data_obj['chart']['result'][0]['timestamp'] + close_prices = data_obj['chart']['result'][0]['indicators']['quote'][0]['close'] + print('close_price len: ', len(close_prices), 'timestamps len: ', len(timestamps)) + # clean out null's and 0s from the data + for i in range(len(timestamps)): + if close_prices[i] == None or close_prices[i] == 0: + del close_prices[i] + del timestamps[i] + i -= 1 + + name = data_obj['chart']['result'][0]['meta']['longName'] + + return {'timestamps': timestamps, 'prices': close_prices, 'name': name}
\ No newline at end of file @@ -1,5 +1,7 @@ from dash import Dash, dcc, html, Input, Output -from analysis import calc_emas, find_intersections, interpolate_intersection, calculate_profit +from analysis import find_intersections, interpolate_intersection +from api import fetch_chart_data +from ema import calc_emas, calculate_profit import plotly.graph_objects as go import json import datetime @@ -7,71 +9,97 @@ import datetime app = Dash(__name__) # pull stock data from json files -timestamps_file = open('timestamps.json', 'r') -timestamps_file_data = timestamps_file.read() -timestamps_raw = json.loads(timestamps_file_data) -timestamps = [datetime.datetime.fromtimestamp(t) for t in timestamps_raw] +# timestamps_file = open('timestamps.json', 'r') +# timestamps_file_data = timestamps_file.read() +# 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()) +# prices_file = open('close_prices.json', 'r') +# prices = json.loads(prices_file.read()) -ema_5 = calc_emas(5, prices) -ema_13 = calc_emas(13, prices) - -intersection_indices = find_intersections(ema_5, ema_13, offset=13) # offset so don't calculate the SMA days -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) - -profit = calculate_profit(ema_5, ema_13, prices, timestamps, 13) -buy_info = profit[-2] -buy_x = [] -buy_y = [] -for x,y,_ in buy_info: - buy_x.append(x) - buy_y.append(y) - -sell_info = profit[-1] -sell_x = [] -sell_y = [] -for x,y,_ in sell_info: - sell_x.append(x) - sell_y.append(y) - -print("Result Analysis:\n", "Percent gain/loss:\t", profit[0]) -percent_gain = profit[0] * 100 +# intersection_indices = find_intersections(ema_5, ema_13, offset=13) # offset so don't calculate the SMA days +# 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) +percent_gain = 0 +memo_fig = None app.layout = html.Div([ html.H4('Interactive color selection with simple Dash example'), - html.P("Select color:"), + html.Label("Ticker ", htmlFor="ticker"), + dcc.Input(id="ticker", value="SPY", type="text"), + html.Br(), + html.Label("Period ", htmlFor="period"), dcc.Dropdown( - id="dropdown", - options=['Gold', 'MediumTurquoise', 'LightGreen'], - value='Gold', - clearable=False, + id="period_dropdown", + options=["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"], + value = "1y", ), + html.Br(), + html.Label("Interval ", htmlFor="Interval"), + dcc.Dropdown( + id="interval_dropdown", + options=["1m", "2m", "5m", "15m", "30m", "60m", "90m", "1h", "4h", "1d", "5d", "1wk", "1mo", "3mo"], + value = "1d", + ), + html.Hr(), dcc.Graph(id="graph"), - html.P("If bought and sold on these signals, the percent gain/loss would be: " + str(round(percent_gain, 4))) + html.P("If bought and sold on these signals, the percent gain/loss would be: " + str(percent_gain)) ]) @app.callback( Output("graph", "figure"), - Input("dropdown", "value")) -def display_color(color): - fig = go.Figure( - [ - go.Scatter(name='Price', x=timestamps, y=prices, line=dict(color='rgb(0, 255, 0)'), mode='lines'), - # go.Scatter(name='5 day EMA', x=timestamps, y=ema_5, line=dict(color='rgb(0, 255, 0)'), mode='lines'), - # go.Scatter(name='13 day EMA', x=timestamps, y=ema_13, line=dict(color='rgb(0, 0, 255)'), mode='lines'), - # go.Scatter(name='EMA Intersections', x=intersected_x, y=intersected_y, line=dict(color='rgb(255, 0, 0)'), mode='markers') - go.Scatter(name='Buys', x=buy_x, y=buy_y, line=dict(color='rgb(0, 0, 255)'), mode='markers', marker_size=10), - go.Scatter(name='Sells', x=sell_x, y=sell_y, line=dict(color='rgb(255, 255, 0)'), mode='markers', marker_size=10), - ] + Input("ticker", "value"), + Input("period_dropdown", "value"), + Input("interval_dropdown", "value") ) - return fig +def display_color(ticker, period, interval): + try: + chart_data = fetch_chart_data(ticker, period, interval) + except: + return memo_fig + else: + timestamps_raw = chart_data['timestamps'] + timestamps = [datetime.datetime.fromtimestamp(t) for t in timestamps_raw] + prices = chart_data['prices'] + + ema_5 = calc_emas(5, prices) + ema_13 = calc_emas(13, prices) + profit = calculate_profit(ema_5, ema_13, prices, timestamps, 13) + buy_info = profit[-2] + buy_x = [] + buy_y = [] + for x,y,_ in buy_info: + buy_x.append(x) + buy_y.append(y) + + sell_info = profit[-1] + sell_x = [] + sell_y = [] + for x,y,_ in sell_info: + sell_x.append(x) + sell_y.append(y) + + print("Result Analysis:\n", "Percent gain/loss:\t", profit[0], profit[1], profit[2]) + percent_gain = profit[0] * 100 + finally: + # Code to execute no matter what (optional) + fig = go.Figure( + [ + go.Scatter(name='Price', x=timestamps, y=prices, line=dict(color='rgb(0, 0, 0)'), mode='lines'), + # go.Scatter(name='5 day EMA', x=timestamps, y=ema_5, line=dict(color='rgb(0, 255, 0)'), mode='lines'), + # go.Scatter(name='13 day EMA', x=timestamps, y=ema_13, line=dict(color='rgb(0, 0, 255)'), mode='lines'), + # go.Scatter(name='EMA Intersections', x=intersected_x, y=intersected_y, line=dict(color='rgb(255, 0, 0)'), mode='markers'), + go.Scatter(name='Buys', x=buy_x, y=buy_y, line=dict(color='rgb(0, 0, 255)'), mode='markers', marker_size=10), + go.Scatter(name='Sells', x=sell_x, y=sell_y, line=dict(color='rgb(255, 255, 0)'), mode='markers', marker_size=10), + ] + ) + print(ticker, period, interval) + memo_fig = fig + return fig app.run(debug=True)
\ No newline at end of file @@ -0,0 +1,63 @@ +def compute_buy_sell_signals_ema(period1, period2, prices): + ema_5 = calc_emas(5, prices) + ema_13 = calc_emas(13, prices) + + +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] + if prices[i] == None or prices[i] == 0: + print(i) + next_ema = prices[i] * weighted_multiplier + last_ema * (1 - weighted_multiplier) + emas.append(next_ema) + return emas + +def calculate_profit(buy_line, sell_line, prices, timestamps, offset=0, starting_money=10000): + if len(buy_line) != len(sell_line): + print("ERROR IN find_intersections: len of arrs not the same") + return [] + is_bought = False + curr_money = 10000 + shares_owned = 0 + buy_info = [] # coming in, (time, cash, # current shares, # shares to buy) + sell_info = [] # (time, cash, # current shares, # shares to sell,) + for i in range(offset, len(buy_line)): + current_b1 = buy_line[i] + current_sl = sell_line[i] + # if the sign is positive, we want to hold, if it's negative, we want to sell + sign_signal = current_b1 - current_sl + + if sign_signal > 0: + if not is_bought: + # buy the stock + shares_owned = curr_money / prices[i] + curr_money = 0 + buy_info.append((timestamps[i], prices[i], i)) + is_bought = True + if sign_signal < 0: + if is_bought: + # selling the stock + curr_money = prices[i] * shares_owned + shares_owned = 0 + sell_info.append((timestamps[i], prices[i], i)) + is_bought = False + + # TODO: consider end interval + total_assets = prices[-1] * shares_owned + curr_money + percent_gain = (total_assets - starting_money) / starting_money + return (percent_gain, total_assets, starting_money, buy_info, sell_info)
\ No newline at end of file @@ -2,19 +2,17 @@ import requests import json import datetime -print("hello") - """ First pull data from yahoo api """ params = { #'period1' : '1753487940', #'period2' : '1753725600', - 'interval' : '1d', - 'range' : '1y', + 'interval' : '1wk', # 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 4h, 1d, 5d, 1wk, 1mo, 3mo + 'range' : '5y', # "1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max" 'events' : 'div|split|earn', 'includePrePost' : 'false' } headers = {'User-agent' : 'fin-backtesting-proj'} -r = requests.get("https://query2.finance.yahoo.com/v8/finance/chart/AAPL", headers=headers, params=params) +r = requests.get("https://query2.finance.yahoo.com/v8/finance/chart/SPY", headers=headers, params=params) print(r.url) print("status_code:\t", r.status_code) @@ -28,6 +26,15 @@ timestamps = data_obj['chart']['result'][0]['timestamp'] close_prices = data_obj['chart']['result'][0]['indicators']['quote'][0]['close'] print('close_price len: ', len(close_prices), 'timestamps len: ', len(timestamps)) +# clean out null's and 0s +for i in range(len(timestamps)): + if close_prices[i] == None or close_prices[i] == 0: + del close_prices[i] + del timestamps[i] + i -= 1 + +print('close_price len: ', len(close_prices), 'timestamps len: ', len(timestamps)) + # save timestamps and close prices into separate files timestamps_encoded = json.dumps(timestamps) close_prices_encoded = json.dumps(close_prices) |