sukiboo commited on
Commit
dbff4bd
Β·
1 Parent(s): 4f627a5

fix price normalization

Browse files
Files changed (4) hide show
  1. app.ipynb +2 -23
  2. src/dash_app.py +9 -34
  3. src/style_elements.py +10 -6
  4. src/utils.py +22 -0
app.ipynb CHANGED
@@ -18,13 +18,6 @@
18
  "id": "5d3ea548",
19
  "metadata": {},
20
  "outputs": [
21
- {
22
- "name": "stdout",
23
- "output_type": "stream",
24
- "text": [
25
- "plot_prices: date_range=[None, None]\n"
26
- ]
27
- },
28
  {
29
  "data": {
30
  "text/html": [
@@ -40,7 +33,7 @@
40
  " "
41
  ],
42
  "text/plain": [
43
- "<IPython.lib.display.IFrame at 0x7f69486ff1c0>"
44
  ]
45
  },
46
  "metadata": {},
@@ -51,21 +44,7 @@
51
  "output_type": "stream",
52
  "text": [
53
  "Selected tickers: AAPL, GOOGL, MSFT\n",
54
- "Selected tickers: AAPL, GOOGL, MSFT\n",
55
- "plot_prices: date_range=['1980-12-12', '2025-01-31']\n",
56
- "Selected tickers: AAPL, GOOGL, MSFT\n",
57
- "plot_prices: date_range=['1991-02-08 09:20', '2025-01-31']\n",
58
- "Selected tickers: AAPL, GOOGL, MSFT\n",
59
- "plot_prices: date_range=['1991-04-13 08:40', '2025-01-31']\n",
60
- "Selected tickers: AAPL, GOOGL, MSFT\n",
61
- "plot_prices: date_range=['1991-04-13 08:40', '2009-10-25 01:46:40']\n",
62
- "Selected tickers: AAPL, GOOGL, MSFT\n",
63
- "plot_prices: date_range=['1998-04-16 20:54:30.1644', '2005-03-24 20:59:54.675']\n",
64
- "Selected tickers: AAPL, GOOGL, MSFT\n",
65
- "['2004-03-24', '2005-03-24 20:59:54.675']\n",
66
- "plot_prices: date_range=['2004-03-24', '2005-03-24 20:59:54.675']\n",
67
- "Selected tickers: AAPL, GOOGL, MSFT\n",
68
- "['2004-03-24', '2005-03-24 20:59:54.675']\n"
69
  ]
70
  }
71
  ],
 
18
  "id": "5d3ea548",
19
  "metadata": {},
20
  "outputs": [
 
 
 
 
 
 
 
21
  {
22
  "data": {
23
  "text/html": [
 
33
  " "
34
  ],
35
  "text/plain": [
36
+ "<IPython.lib.display.IFrame at 0x7fd23f91ee60>"
37
  ]
38
  },
39
  "metadata": {},
 
44
  "output_type": "stream",
45
  "text": [
46
  "Selected tickers: AAPL, GOOGL, MSFT\n",
47
+ "Selected tickers: AAPL, GOOGL, MSFT\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  ]
49
  }
50
  ],
src/dash_app.py CHANGED
@@ -1,8 +1,4 @@
1
- from datetime import timedelta
2
-
3
- import numpy as np
4
  from dash import Dash, Input, Output, State, ctx, dcc, html
5
- from dateutil import parser # type: ignore
6
 
7
  from src.prices import get_historical_prices
8
  from src.style_elements import (
@@ -10,13 +6,13 @@ from src.style_elements import (
10
  setup_interval_buttons,
11
  setup_ticker_selection,
12
  )
13
- from src.utils import date_to_idx_range, get_date_range
14
 
15
 
16
  class NormalizedAssetPricesApp:
17
 
18
- # TODO: add initial interval
19
- def __init__(self, initial_tickers=["AAPL", "GOOGL", "MSFT"]):
20
 
21
  self.setup_env(initial_tickers)
22
 
@@ -34,31 +30,18 @@ class NormalizedAssetPricesApp:
34
  self.rolling_changes = self.percentage_changes.rolling(window=251, min_periods=1).sum()
35
  self.timestamps = self.prices.index
36
  self.idx_range = [0, -1]
37
- self.normalize_prices()
38
- # TODO: this is horrible, pls fix
39
- self.fig = plot_prices(
40
- self.timestamps, self.prices, self.prices_normalized, self.rolling_changes, [None, None]
41
- )
42
 
43
- def normalize_prices(self):
44
- idx0, idx1 = self.idx_range
45
- date0, date1 = self.timestamps[idx0], self.timestamps[idx1]
46
- price = self.prices.loc[date0]
47
- self.prices_normalized = np.nan * self.prices
48
- self.prices_normalized.loc[date0:date1] = 100 * (self.prices[date0:date1] / price - 1)
49
 
50
  def update_figure(self, date_range=[None, None]):
51
  idx_range = date_to_idx_range(self.timestamps, date_range)
52
- # do not update the figure if the range is unchanged
53
  if idx_range != self.idx_range:
54
  self.idx_range = idx_range
55
- self.normalize_prices()
56
  self.fig = plot_prices(
57
  self.timestamps,
58
  self.prices,
59
- self.prices_normalized,
60
  self.rolling_changes,
61
- date_range,
62
  )
63
  return self.fig
64
 
@@ -107,22 +90,14 @@ class NormalizedAssetPricesApp:
107
  date_range = get_date_range(current_figure["layout"])
108
  triggered_id = ctx.triggered_id
109
  if triggered_id in self.interval_buttons_ids:
110
- date_range = self.adjust_date_range(date_range, triggered_id)
111
- print(date_range)
 
 
112
 
113
  fig = self.update_figure(date_range)
114
  return fig
115
 
116
- # TODO: filter date_range=[None, None]
117
- # UPD: might have fixed it by setting the initial idx range to [0, -1]
118
- def adjust_date_range(self, date_range, button_id):
119
- start_date, end_date = date_range
120
- start_date = max(
121
- parser.parse(end_date) - timedelta(days=self.interval_offsets[button_id]),
122
- self.timestamps[0],
123
- ).strftime("%Y-%m-%d")
124
- return [start_date, end_date]
125
-
126
  def run(self, **kwargs):
127
  self.app.run_server(**kwargs)
128
 
 
 
 
 
1
  from dash import Dash, Input, Output, State, ctx, dcc, html
 
2
 
3
  from src.prices import get_historical_prices
4
  from src.style_elements import (
 
6
  setup_interval_buttons,
7
  setup_ticker_selection,
8
  )
9
+ from src.utils import adjust_date_range, date_to_idx_range, get_date_range
10
 
11
 
12
  class NormalizedAssetPricesApp:
13
 
14
+ # TODO: add initial interval via self.idx_range
15
+ def __init__(self, initial_tickers=["AAPL", "GOOGL", "MSFT"], initial_interval="1y"):
16
 
17
  self.setup_env(initial_tickers)
18
 
 
30
  self.rolling_changes = self.percentage_changes.rolling(window=251, min_periods=1).sum()
31
  self.timestamps = self.prices.index
32
  self.idx_range = [0, -1]
 
 
 
 
 
33
 
34
+ self.fig = plot_prices(self.timestamps, self.prices, self.rolling_changes, self.idx_range)
 
 
 
 
 
35
 
36
  def update_figure(self, date_range=[None, None]):
37
  idx_range = date_to_idx_range(self.timestamps, date_range)
 
38
  if idx_range != self.idx_range:
39
  self.idx_range = idx_range
 
40
  self.fig = plot_prices(
41
  self.timestamps,
42
  self.prices,
 
43
  self.rolling_changes,
44
+ idx_range,
45
  )
46
  return self.fig
47
 
 
90
  date_range = get_date_range(current_figure["layout"])
91
  triggered_id = ctx.triggered_id
92
  if triggered_id in self.interval_buttons_ids:
93
+ date_range = adjust_date_range(
94
+ self.timestamps, self.interval_offsets, date_range, triggered_id
95
+ )
96
+ print(f"interval update: {date_range=}")
97
 
98
  fig = self.update_figure(date_range)
99
  return fig
100
 
 
 
 
 
 
 
 
 
 
 
101
  def run(self, **kwargs):
102
  self.app.run_server(**kwargs)
103
 
src/style_elements.py CHANGED
@@ -5,6 +5,7 @@ import plotly.graph_objects as go
5
  from dash import dcc, html
6
 
7
  from src.prices import get_available_tickers
 
8
 
9
 
10
  def setup_ticker_selection(initial_tickers):
@@ -64,8 +65,11 @@ def setup_interval_buttons():
64
  return interval_buttons_html, interval_buttons_ids, interval_offsets
65
 
66
 
67
- def plot_prices(timestamps, prices, prices_normalized, rolling_changes, date_range):
68
- print(f"plot_prices: {date_range=}")
 
 
 
69
  fig = go.Figure()
70
 
71
  # rangeslider plot
@@ -88,7 +92,7 @@ def plot_prices(timestamps, prices, prices_normalized, rolling_changes, date_ran
88
  fig.add_trace(
89
  go.Scatter(
90
  x=timestamps,
91
- y=prices_normalized[asset],
92
  line=dict(width=3, color=next(colors)),
93
  name=asset,
94
  xaxis="x2",
@@ -111,11 +115,11 @@ def plot_prices(timestamps, prices, prices_normalized, rolling_changes, date_ran
111
  # configure axes
112
  xaxis1_dict = dict(rangeslider=dict(visible=True, thickness=0.1), tickangle=-30, nticks=20)
113
  xaxis2_dict = dict(matches="x1", showticklabels=False)
114
- if all(date_range):
115
- xaxis1_dict["range"] = date_range
116
- xaxis2_dict["range"] = date_range
117
  yaxis1_dict = dict(showticklabels=False)
118
  yaxis2_dict = dict(
 
119
  title="relative price change",
120
  nticks=12,
121
  tickformat=".0f",
 
5
  from dash import dcc, html
6
 
7
  from src.prices import get_available_tickers
8
+ from src.utils import normalize_prices
9
 
10
 
11
  def setup_ticker_selection(initial_tickers):
 
65
  return interval_buttons_html, interval_buttons_ids, interval_offsets
66
 
67
 
68
+ def plot_prices(timestamps, prices, rolling_changes, idx_range):
69
+ idx0, idx1 = idx_range
70
+ date_range = [timestamps[idx0], timestamps[idx1]]
71
+ prices_normalized = normalize_prices(prices, date_range)
72
+
73
  fig = go.Figure()
74
 
75
  # rangeslider plot
 
92
  fig.add_trace(
93
  go.Scatter(
94
  x=timestamps,
95
+ y=100 * prices_normalized[asset],
96
  line=dict(width=3, color=next(colors)),
97
  name=asset,
98
  xaxis="x2",
 
115
  # configure axes
116
  xaxis1_dict = dict(rangeslider=dict(visible=True, thickness=0.1), tickangle=-30, nticks=20)
117
  xaxis2_dict = dict(matches="x1", showticklabels=False)
118
+ xaxis1_dict["range"] = date_range
119
+ xaxis2_dict["range"] = date_range
 
120
  yaxis1_dict = dict(showticklabels=False)
121
  yaxis2_dict = dict(
122
+ autorange=True,
123
  title="relative price change",
124
  nticks=12,
125
  tickformat=".0f",
src/utils.py CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  def date_to_idx_range(timestamps, date_range):
2
  idx_range = (
3
  timestamps.get_indexer(date_range, method="nearest").tolist()
@@ -18,3 +24,19 @@ def get_date_range(figure_layout):
18
  # else:
19
  # print(figure_layout)
20
  return date_range
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import timedelta
2
+
3
+ import numpy as np
4
+ from dateutil import parser # type: ignore
5
+
6
+
7
  def date_to_idx_range(timestamps, date_range):
8
  idx_range = (
9
  timestamps.get_indexer(date_range, method="nearest").tolist()
 
24
  # else:
25
  # print(figure_layout)
26
  return date_range
27
+
28
+
29
+ def adjust_date_range(timestamps, interval_offsets, date_range, button_id):
30
+ start_date, end_date = date_range
31
+ start_date = max(
32
+ parser.parse(end_date) - timedelta(days=interval_offsets[button_id]),
33
+ timestamps[0],
34
+ ).strftime("%Y-%m-%d")
35
+ return [start_date, end_date]
36
+
37
+
38
+ def normalize_prices(prices, date_range):
39
+ date0, date1 = date_range
40
+ prices_normalized = np.nan * prices
41
+ prices_normalized.loc[date0:date1] = prices[date0:date1] / prices.loc[date0] - 1
42
+ return prices_normalized