aplotly 1.1.22__tar.gz → 1.1.24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aplotly-1.1.22/aplotly.egg-info → aplotly-1.1.24}/PKG-INFO +1 -1
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/colors.py +28 -1
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/plots.py +139 -1
- {aplotly-1.1.22 → aplotly-1.1.24/aplotly.egg-info}/PKG-INFO +1 -1
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/SOURCES.txt +3 -0
- aplotly-1.1.24/examples/fetch_deribit_data.py +51 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_lines.py +4 -3
- aplotly-1.1.24/examples/plot_market.py +384 -0
- aplotly-1.1.24/examples/plot_return_side.py +24 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/pyproject.toml +2 -2
- {aplotly-1.1.22 → aplotly-1.1.24}/LICENSE +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/MANIFEST.in +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/README.md +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/__init__.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/io.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/style.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/utils/return_breakdown.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/dependency_links.txt +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/requires.txt +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/top_level.txt +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_bars.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_confidence_interval.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_correlation.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_exposure_tree.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_gauge.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_line.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_line_and_save.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_lines_with_dist.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_multiple_performance.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_participation.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_performance.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_performance_by_trade.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_returns_breakdown.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_returns_tree.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/setup.cfg +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/tests/test_colors.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/tests/test_io.py +0 -0
- {aplotly-1.1.22 → aplotly-1.1.24}/tests/test_style.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aplotly
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.24
|
|
4
4
|
License: MIT License
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
@@ -126,8 +126,30 @@ def dark_mode():
|
|
|
126
126
|
return line_colors, chart_colors
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
def contrast():
|
|
130
|
+
line_colors = {
|
|
131
|
+
"orange": "#F26451",
|
|
132
|
+
"yellow": "#FFD700",
|
|
133
|
+
"purple": "#9B4F96",
|
|
134
|
+
"teal": "#2A9D8F",
|
|
135
|
+
"indigo": "#4B0082",
|
|
136
|
+
"grey": "#CCCCCC",
|
|
137
|
+
"blue": "#00BFFF",
|
|
138
|
+
"mint": "#98FF98",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
chart_colors = {
|
|
142
|
+
"background": "#000000",
|
|
143
|
+
"grid": "#333333",
|
|
144
|
+
"text": "#E5E5E5",
|
|
145
|
+
"axes": "#E5E5E5",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return line_colors, chart_colors
|
|
149
|
+
|
|
150
|
+
|
|
129
151
|
def available_palettes():
|
|
130
|
-
return ["default", "greys", "night", "dark_mode"]
|
|
152
|
+
return ["default", "greys", "night", "dark_mode", "contrast"]
|
|
131
153
|
|
|
132
154
|
|
|
133
155
|
def select_palette(name, color_type="hex") -> Tuple[dict, dict]:
|
|
@@ -167,5 +189,10 @@ def select_palette(name, color_type="hex") -> Tuple[dict, dict]:
|
|
|
167
189
|
return convert_palette_to_rgba(dark_mode()[0]), convert_palette_to_rgba(dark_mode()[1])
|
|
168
190
|
else:
|
|
169
191
|
return dark_mode()
|
|
192
|
+
elif name == "contrast":
|
|
193
|
+
if color_type == "rgba":
|
|
194
|
+
return convert_palette_to_rgba(contrast()[0]), convert_palette_to_rgba(contrast()[1])
|
|
195
|
+
else:
|
|
196
|
+
return contrast()
|
|
170
197
|
else:
|
|
171
198
|
raise ValueError("palette name not found")
|
|
@@ -114,6 +114,7 @@ def plot_multiple_lines(
|
|
|
114
114
|
labels: list = None,
|
|
115
115
|
visible: list = None,
|
|
116
116
|
styles: list = None,
|
|
117
|
+
colors: list = None,
|
|
117
118
|
legend: bool = True,
|
|
118
119
|
color_palette: str = "",
|
|
119
120
|
group_title: str = "",
|
|
@@ -127,6 +128,8 @@ def plot_multiple_lines(
|
|
|
127
128
|
xlabel (str): x-axis label.
|
|
128
129
|
labels (list, optional): name of each line. Defaults to None (doesn't label of the lines).
|
|
129
130
|
visible (list, optional): if the lines should be visible by default. Defaults to None (all lines are visible).
|
|
131
|
+
styles (list, optional): list of line style dictionaries. Defaults to None.
|
|
132
|
+
colors (list, optional): list of colors for each line. Defaults to None (uses default color palette).
|
|
130
133
|
legend (bool, optional): if the legend should be shown. Defaults to True.
|
|
131
134
|
color_palette (str, optional): name of the color palette to use. Defaults to "" (default color palette).
|
|
132
135
|
group_title (str, optional): name of the legend group. Defaults to "" (no group name).
|
|
@@ -144,12 +147,19 @@ def plot_multiple_lines(
|
|
|
144
147
|
if styles is None:
|
|
145
148
|
styles = [{"width": 2}] * len(series)
|
|
146
149
|
|
|
150
|
+
if colors is None:
|
|
151
|
+
colors = [None] * len(series)
|
|
152
|
+
|
|
147
153
|
if any(visible) is False:
|
|
148
154
|
legend = True
|
|
149
155
|
|
|
150
156
|
configure_plotly(subplots=1, color_palette=color_palette)
|
|
151
157
|
fig = go.Figure()
|
|
152
|
-
for data, label, visibility, style in zip(series, labels, visible, styles):
|
|
158
|
+
for data, label, visibility, style, color in zip(series, labels, visible, styles, colors):
|
|
159
|
+
if color is not None:
|
|
160
|
+
style = style.copy() if style is not None else {}
|
|
161
|
+
style["color"] = color
|
|
162
|
+
|
|
153
163
|
fig.add_trace(
|
|
154
164
|
go.Scatter(
|
|
155
165
|
x=data.index,
|
|
@@ -1103,3 +1113,131 @@ def plot_confidence_intervals(
|
|
|
1103
1113
|
)
|
|
1104
1114
|
|
|
1105
1115
|
return fig
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def plot_benchmark_portfolio_returns(
|
|
1119
|
+
benchmark_returns: pd.Series,
|
|
1120
|
+
portfolio_returns: pd.Series,
|
|
1121
|
+
positive_months: bool = True,
|
|
1122
|
+
color_palette: str = "",
|
|
1123
|
+
plot_title: str = None,
|
|
1124
|
+
benchmark_name: str = "Benchmark",
|
|
1125
|
+
portfolio_name: str = "Portfolio",
|
|
1126
|
+
) -> go.Figure:
|
|
1127
|
+
"""Plot a double bar chart showing benchmark returns and portfolio returns for either positive or negative months.
|
|
1128
|
+
|
|
1129
|
+
Args:
|
|
1130
|
+
benchmark_returns (pd.Series): Series containing benchmark returns data.
|
|
1131
|
+
portfolio_returns (pd.Series): Series containing portfolio returns data.
|
|
1132
|
+
positive_months (bool, optional): If True, show positive months; if False, show negative months. Defaults to True.
|
|
1133
|
+
color_palette (str, optional): Color palette to use. Defaults to "".
|
|
1134
|
+
plot_title (str, optional): Title for the plot. Defaults to None (no title).
|
|
1135
|
+
benchmark_name (str, optional): Name of the benchmark for display. Defaults to "Benchmark".
|
|
1136
|
+
portfolio_name (str, optional): Name of the portfolio for display. Defaults to "Portfolio".
|
|
1137
|
+
|
|
1138
|
+
Returns:
|
|
1139
|
+
go.Figure: Plotly figure containing the double bar chart.
|
|
1140
|
+
"""
|
|
1141
|
+
# Configure plotly style
|
|
1142
|
+
configure_plotly(subplots=2, color_palette=color_palette)
|
|
1143
|
+
|
|
1144
|
+
# Create subplot with shared x-axis and small gap
|
|
1145
|
+
fig = make_subplots(
|
|
1146
|
+
rows=2,
|
|
1147
|
+
cols=1,
|
|
1148
|
+
shared_xaxes=True,
|
|
1149
|
+
vertical_spacing=0.05, # Add small gap between plots
|
|
1150
|
+
row_heights=[0.5, 0.5],
|
|
1151
|
+
subplot_titles=("", "") # Remove subplot titles from top
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
# Filter for positive or negative months based on benchmark returns
|
|
1155
|
+
if positive_months:
|
|
1156
|
+
filtered_months = benchmark_returns[benchmark_returns > 0].index
|
|
1157
|
+
title_suffix = "Positive"
|
|
1158
|
+
else:
|
|
1159
|
+
filtered_months = benchmark_returns[benchmark_returns < 0].index
|
|
1160
|
+
title_suffix = "Negative"
|
|
1161
|
+
|
|
1162
|
+
# Get the filtered data
|
|
1163
|
+
filtered_benchmark = benchmark_returns.loc[filtered_months]
|
|
1164
|
+
filtered_portfolio = portfolio_returns.loc[filtered_months]
|
|
1165
|
+
|
|
1166
|
+
# Define colors
|
|
1167
|
+
benchmark_color = "orange"
|
|
1168
|
+
portfolio_color = "blue"
|
|
1169
|
+
portfolio_negative_color = "darkblue" # Darker blue for negative portfolio returns
|
|
1170
|
+
|
|
1171
|
+
# Add benchmark returns bar chart
|
|
1172
|
+
fig.add_trace(
|
|
1173
|
+
go.Bar(
|
|
1174
|
+
x=filtered_benchmark.index,
|
|
1175
|
+
y=filtered_benchmark * 100, # Convert to percentage
|
|
1176
|
+
name=f"{benchmark_name} Returns",
|
|
1177
|
+
marker_color=benchmark_color,
|
|
1178
|
+
),
|
|
1179
|
+
row=1, col=1
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
# Split portfolio returns into positive and negative for different colors
|
|
1183
|
+
positive_portfolio = filtered_portfolio[filtered_portfolio > 0]
|
|
1184
|
+
negative_portfolio = filtered_portfolio[filtered_portfolio < 0]
|
|
1185
|
+
|
|
1186
|
+
# Add positive portfolio returns bar chart
|
|
1187
|
+
if not positive_portfolio.empty:
|
|
1188
|
+
fig.add_trace(
|
|
1189
|
+
go.Bar(
|
|
1190
|
+
x=positive_portfolio.index,
|
|
1191
|
+
y=positive_portfolio * 100, # Convert to percentage
|
|
1192
|
+
name=f"{portfolio_name} Positive Returns",
|
|
1193
|
+
marker_color=portfolio_color,
|
|
1194
|
+
),
|
|
1195
|
+
row=2, col=1
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
# Add negative portfolio returns bar chart with darker color
|
|
1199
|
+
if not negative_portfolio.empty:
|
|
1200
|
+
fig.add_trace(
|
|
1201
|
+
go.Bar(
|
|
1202
|
+
x=negative_portfolio.index,
|
|
1203
|
+
y=negative_portfolio * 100, # Convert to percentage
|
|
1204
|
+
name=f"{portfolio_name} Negative Returns",
|
|
1205
|
+
marker_color=portfolio_negative_color,
|
|
1206
|
+
),
|
|
1207
|
+
row=2, col=1
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
# Update layout
|
|
1211
|
+
if plot_title is not None:
|
|
1212
|
+
title_text = f"{plot_title} ({title_suffix} {benchmark_name} Months)"
|
|
1213
|
+
else:
|
|
1214
|
+
title_text = None
|
|
1215
|
+
|
|
1216
|
+
fig.update_layout(
|
|
1217
|
+
title_text=title_text,
|
|
1218
|
+
showlegend=False,
|
|
1219
|
+
height=800,
|
|
1220
|
+
margin=dict(l=50, r=50, t=50, b=50), # Standard margins
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
# Update x-axis label
|
|
1224
|
+
fig.update_xaxes(title_text="Month", row=2, col=1)
|
|
1225
|
+
|
|
1226
|
+
# Add y-axis labels with custom colors
|
|
1227
|
+
fig.update_yaxes(
|
|
1228
|
+
title_text=f"{benchmark_name} Return (%)",
|
|
1229
|
+
row=1,
|
|
1230
|
+
col=1,
|
|
1231
|
+
title_font=dict(color=benchmark_color),
|
|
1232
|
+
title_standoff=15
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
fig.update_yaxes(
|
|
1236
|
+
title_text=f"{portfolio_name} Return (%)",
|
|
1237
|
+
row=2,
|
|
1238
|
+
col=1,
|
|
1239
|
+
title_font=dict(color=portfolio_color),
|
|
1240
|
+
title_standoff=15
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
return fig
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aplotly
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.24
|
|
4
4
|
License: MIT License
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
@@ -13,6 +13,7 @@ aplotly.egg-info/dependency_links.txt
|
|
|
13
13
|
aplotly.egg-info/requires.txt
|
|
14
14
|
aplotly.egg-info/top_level.txt
|
|
15
15
|
aplotly/utils/return_breakdown.py
|
|
16
|
+
examples/fetch_deribit_data.py
|
|
16
17
|
examples/plot_bars.py
|
|
17
18
|
examples/plot_confidence_interval.py
|
|
18
19
|
examples/plot_correlation.py
|
|
@@ -22,10 +23,12 @@ examples/plot_line.py
|
|
|
22
23
|
examples/plot_line_and_save.py
|
|
23
24
|
examples/plot_lines.py
|
|
24
25
|
examples/plot_lines_with_dist.py
|
|
26
|
+
examples/plot_market.py
|
|
25
27
|
examples/plot_multiple_performance.py
|
|
26
28
|
examples/plot_participation.py
|
|
27
29
|
examples/plot_performance.py
|
|
28
30
|
examples/plot_performance_by_trade.py
|
|
31
|
+
examples/plot_return_side.py
|
|
29
32
|
examples/plot_returns_breakdown.py
|
|
30
33
|
examples/plot_returns_tree.py
|
|
31
34
|
tests/test_colors.py
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
|
|
5
|
+
def fetch_deribit_data(endpoint, params=None):
|
|
6
|
+
"""
|
|
7
|
+
Fetch data from Deribit API
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
endpoint (str): API endpoint
|
|
11
|
+
params (dict): Query parameters
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
dict: API response
|
|
15
|
+
"""
|
|
16
|
+
base_url = "https://test.deribit.com/api/v2"
|
|
17
|
+
url = f"{base_url}{endpoint}"
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
response = requests.get(url, params=params)
|
|
21
|
+
response.raise_for_status() # Raise an exception for bad status codes
|
|
22
|
+
return response.json()
|
|
23
|
+
except requests.exceptions.RequestException as e:
|
|
24
|
+
print(f"Error fetching data: {e}")
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def main():
|
|
28
|
+
# Example: Fetch volatility index data
|
|
29
|
+
endpoint = "/public/get_volatility_index_data"
|
|
30
|
+
|
|
31
|
+
# Set up parameters for the last 7 days
|
|
32
|
+
end_time = int(datetime.now().timestamp() * 1000)
|
|
33
|
+
start_time = int((datetime.now() - timedelta(days=7)).timestamp() * 1000)
|
|
34
|
+
|
|
35
|
+
params = {
|
|
36
|
+
"index_name": "btc_usd", # Bitcoin volatility index
|
|
37
|
+
"start_timestamp": start_time,
|
|
38
|
+
"end_timestamp": end_time,
|
|
39
|
+
"resolution": 60 # Daily data
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
data = fetch_deribit_data(endpoint, params)
|
|
43
|
+
|
|
44
|
+
if data:
|
|
45
|
+
print("Successfully fetched data:")
|
|
46
|
+
print(json.dumps(data, indent=2))
|
|
47
|
+
else:
|
|
48
|
+
print("Failed to fetch data")
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -4,10 +4,11 @@ import pandas as pd
|
|
|
4
4
|
from aplotly.plots import plot_multiple_lines
|
|
5
5
|
|
|
6
6
|
fig = plot_multiple_lines(
|
|
7
|
-
[pd.Series(np.random.rand(100), index=np.arange(100)) for _ in range(
|
|
8
|
-
labels=["Test 1", "Test 2", "Test 3"],
|
|
9
|
-
styles=[{"shape": "hv"}, {"shape": "vh"}, {"shape": "linear"}],
|
|
7
|
+
[pd.Series(np.random.rand(100), index=np.arange(100)) for _ in range(8)],
|
|
8
|
+
labels=["Test 1", "Test 2", "Test 3", "Test 4", "Test 5", "Test 6", "Test 7", "Test 8"],
|
|
9
|
+
styles=[{"shape": "hv"}, {"shape": "vh"}, {"shape": "linear"}, {"shape": "hv"}, {"shape": "vh"}, {"shape": "linear"}, {"shape": "hv"}, {"shape": "vh"}],
|
|
10
10
|
xlabel="X",
|
|
11
11
|
ylabel="Y",
|
|
12
|
+
color_palette="contrast",
|
|
12
13
|
)
|
|
13
14
|
fig.show()
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import yfinance as yf
|
|
4
|
+
import plotly.graph_objects as go
|
|
5
|
+
from aplotly.style import configure_plotly
|
|
6
|
+
import numpy as np
|
|
7
|
+
from aplotly import save_figure
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fetch_data(symbol: str, start: str = '2020-01-01', end: str = '2025-12-31'):
|
|
11
|
+
"""Fetch price data from Yahoo Finance."""
|
|
12
|
+
data = yf.download(symbol, start=start, end=end)
|
|
13
|
+
data.columns = data.columns.get_level_values(0)
|
|
14
|
+
data.columns.name = None
|
|
15
|
+
data.reset_index(inplace=True)
|
|
16
|
+
data['symbol'] = symbol
|
|
17
|
+
return data
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def calculate_mad(series, window=90):
|
|
21
|
+
"""Calculate Median Absolute Deviation (MAD)."""
|
|
22
|
+
# Calculate the rolling median
|
|
23
|
+
rolling_median = series.rolling(window=window).median()
|
|
24
|
+
# Calculate absolute deviations from the median
|
|
25
|
+
abs_deviations = (series - rolling_median) / series.std()
|
|
26
|
+
return abs_deviations
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ===== DATA FETCHING AND PROCESSING =====
|
|
30
|
+
|
|
31
|
+
# Fetch volatility index data from Deribit
|
|
32
|
+
url = "https://test.deribit.com/api/v2/public/get_volatility_index_data"
|
|
33
|
+
params = {
|
|
34
|
+
"currency": "BTC",
|
|
35
|
+
"start_timestamp": 0,
|
|
36
|
+
"end_timestamp": 2099376800000,
|
|
37
|
+
"resolution": "1D"
|
|
38
|
+
}
|
|
39
|
+
headers = {"Content-Type": "application/json"}
|
|
40
|
+
|
|
41
|
+
response = requests.get(url, params=params, headers=headers)
|
|
42
|
+
response.raise_for_status()
|
|
43
|
+
|
|
44
|
+
# Process volatility data
|
|
45
|
+
df = pd.DataFrame(response.json()['result']['data'],
|
|
46
|
+
columns=['timestamp', 'open', 'high', 'low', 'close']).set_index('timestamp')
|
|
47
|
+
df.index = pd.to_datetime(df.index, unit='ms')
|
|
48
|
+
|
|
49
|
+
# Fetch and process price data
|
|
50
|
+
price_data = fetch_data('BTC-USD').set_index('Date')['Close']
|
|
51
|
+
price_data = calculate_mad(price_data)
|
|
52
|
+
price_data = price_data.resample("1W").last()
|
|
53
|
+
|
|
54
|
+
# Align data
|
|
55
|
+
shared_indices = price_data.index.intersection(df.index)
|
|
56
|
+
df = df.loc[shared_indices]
|
|
57
|
+
price_data = price_data.loc[shared_indices]
|
|
58
|
+
|
|
59
|
+
# ===== EVENT MARKERS =====
|
|
60
|
+
|
|
61
|
+
# Define event dates
|
|
62
|
+
event_dates = {
|
|
63
|
+
'FTX Crisis': '2022-11-13',
|
|
64
|
+
'Dollar Yen Crash': '2024-08-04',
|
|
65
|
+
'US Tariffs': '2025-04-06',
|
|
66
|
+
'Bybit Hack': '2025-02-21'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Process event data
|
|
70
|
+
event_data = {}
|
|
71
|
+
for event_name, date_str in event_dates.items():
|
|
72
|
+
event_date = pd.Timestamp(date_str)
|
|
73
|
+
closest_date = df.index[df.index.get_indexer([event_date], method='nearest')[0]]
|
|
74
|
+
event_data[event_name] = {
|
|
75
|
+
'date': closest_date,
|
|
76
|
+
'volatility': df.loc[closest_date, 'close'],
|
|
77
|
+
'return': price_data.loc[closest_date]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Get the most recent data point
|
|
81
|
+
most_recent_volatility = df['close'].iloc[-1]
|
|
82
|
+
most_recent_return = price_data.iloc[-1]
|
|
83
|
+
event_data['Current'] = {
|
|
84
|
+
'volatility': most_recent_volatility,
|
|
85
|
+
'return': most_recent_return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Filter data to remove anything before the first week of FTX
|
|
89
|
+
ftx_date = event_data['FTX Crisis']['date']
|
|
90
|
+
df = df[df.index >= ftx_date]
|
|
91
|
+
price_data = price_data[price_data.index >= ftx_date]
|
|
92
|
+
|
|
93
|
+
# ===== PLOT CREATION =====
|
|
94
|
+
|
|
95
|
+
# Configure plotly style
|
|
96
|
+
configure_plotly(subplots=1, color_palette="")
|
|
97
|
+
|
|
98
|
+
# Create scatter plot
|
|
99
|
+
fig = go.Figure()
|
|
100
|
+
|
|
101
|
+
# Add main scatter plot
|
|
102
|
+
fig.add_trace(
|
|
103
|
+
go.Scatter(
|
|
104
|
+
y=np.log10(df['close']),
|
|
105
|
+
x=price_data,
|
|
106
|
+
mode='markers',
|
|
107
|
+
marker=dict(
|
|
108
|
+
size=6,
|
|
109
|
+
color='grey',
|
|
110
|
+
opacity=0.7
|
|
111
|
+
),
|
|
112
|
+
showlegend=False
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Prepare marker data
|
|
117
|
+
marker_x = [event_data[event]['volatility'] for event in event_data]
|
|
118
|
+
marker_y = [event_data[event]['return'] for event in event_data]
|
|
119
|
+
marker_text = list(event_data.keys())
|
|
120
|
+
marker_colors = ['orange'] * (len(event_data) - 1) + ['red'] # All orange except the last one (Current)
|
|
121
|
+
marker_symbols = ['diamond'] * (len(event_data) - 1) + ['star'] # All diamond except the last one (Current)
|
|
122
|
+
|
|
123
|
+
# Add markers
|
|
124
|
+
fig.add_trace(
|
|
125
|
+
go.Scatter(
|
|
126
|
+
y=np.log10(marker_x),
|
|
127
|
+
x=marker_y,
|
|
128
|
+
mode='markers',
|
|
129
|
+
marker=dict(
|
|
130
|
+
size=8,
|
|
131
|
+
color=marker_colors,
|
|
132
|
+
symbol=marker_symbols
|
|
133
|
+
),
|
|
134
|
+
showlegend=False
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# ===== AXIS SETUP =====
|
|
139
|
+
|
|
140
|
+
# Calculate y-axis values
|
|
141
|
+
y_min = np.log10(df['close'].min())
|
|
142
|
+
y_max = np.log10(df['close'].max())
|
|
143
|
+
y_range = y_max - y_min
|
|
144
|
+
avg_y = np.log10(df['close'].mean())
|
|
145
|
+
|
|
146
|
+
# Calculate x-axis values
|
|
147
|
+
x_min = -max(abs(price_data.min()), abs(price_data.max()))
|
|
148
|
+
x_max = max(abs(price_data.min()), abs(price_data.max()))
|
|
149
|
+
x_extent = x_max - x_min
|
|
150
|
+
|
|
151
|
+
# Center y-axis on mean
|
|
152
|
+
max_distance_from_mean = max(abs(y_max - avg_y), abs(y_min - avg_y))
|
|
153
|
+
y_min_adjusted = avg_y - max_distance_from_mean
|
|
154
|
+
y_max_adjusted = avg_y + max_distance_from_mean
|
|
155
|
+
y_range_adjusted = y_max_adjusted - y_min_adjusted
|
|
156
|
+
|
|
157
|
+
# Plot dimensions and padding
|
|
158
|
+
plot_width = 1170
|
|
159
|
+
plot_height = 650
|
|
160
|
+
pixel_padding = 5
|
|
161
|
+
x_padding = (x_max - x_min) * (pixel_padding / plot_width)
|
|
162
|
+
y_padding = (y_max_adjusted - y_min_adjusted) * (pixel_padding / plot_height)
|
|
163
|
+
|
|
164
|
+
# ===== SHAPES AND LINES =====
|
|
165
|
+
|
|
166
|
+
# Add vertical line at x=0
|
|
167
|
+
fig.add_shape(
|
|
168
|
+
type="line",
|
|
169
|
+
x0=0,
|
|
170
|
+
y0=y_min_adjusted - y_padding,
|
|
171
|
+
x1=0,
|
|
172
|
+
y1=y_max_adjusted + y_padding,
|
|
173
|
+
line=dict(
|
|
174
|
+
color="black",
|
|
175
|
+
width=1,
|
|
176
|
+
dash="solid",
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Add horizontal line at average y value
|
|
181
|
+
fig.add_shape(
|
|
182
|
+
type="line",
|
|
183
|
+
x0=x_min - x_padding,
|
|
184
|
+
y0=avg_y,
|
|
185
|
+
x1=x_max + x_padding,
|
|
186
|
+
y1=avg_y,
|
|
187
|
+
line=dict(
|
|
188
|
+
color="black",
|
|
189
|
+
width=1,
|
|
190
|
+
dash="solid",
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# ===== ANNOTATIONS =====
|
|
195
|
+
|
|
196
|
+
# Create annotations for event markers
|
|
197
|
+
annotations = []
|
|
198
|
+
for i, (x, y, text) in enumerate(zip(marker_x, marker_y, marker_text)):
|
|
199
|
+
color = marker_colors[i]
|
|
200
|
+
annotations.append(
|
|
201
|
+
dict(
|
|
202
|
+
x=y,
|
|
203
|
+
y=np.log10(x) + (np.log10(max(marker_x)) - np.log10(min(marker_x))) * 0.1,
|
|
204
|
+
text=text,
|
|
205
|
+
showarrow=False,
|
|
206
|
+
font=dict(
|
|
207
|
+
size=12,
|
|
208
|
+
color=color
|
|
209
|
+
),
|
|
210
|
+
bgcolor="white",
|
|
211
|
+
opacity=0.8,
|
|
212
|
+
bordercolor="black",
|
|
213
|
+
borderwidth=0,
|
|
214
|
+
borderpad=4,
|
|
215
|
+
ax=0,
|
|
216
|
+
ay=-30
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Add axis label annotations
|
|
221
|
+
annotations.append(
|
|
222
|
+
dict(
|
|
223
|
+
x=x_min + x_extent * 0.75, # 75% across the x-axis
|
|
224
|
+
y=y_max_adjusted - y_padding * 3, # Move to bottom
|
|
225
|
+
text="Optimistic",
|
|
226
|
+
showarrow=False,
|
|
227
|
+
font=dict(
|
|
228
|
+
size=14,
|
|
229
|
+
color="white"
|
|
230
|
+
),
|
|
231
|
+
bgcolor="black",
|
|
232
|
+
opacity=1.0,
|
|
233
|
+
bordercolor="black",
|
|
234
|
+
borderwidth=0,
|
|
235
|
+
borderpad=4,
|
|
236
|
+
ax=0,
|
|
237
|
+
ay=0
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
annotations.append(
|
|
242
|
+
dict(
|
|
243
|
+
x=x_min + x_extent * 0.25, # 25% across the x-axis
|
|
244
|
+
y=y_max_adjusted - y_padding * 3, # Move to bottom
|
|
245
|
+
text="Pessimistic",
|
|
246
|
+
showarrow=False,
|
|
247
|
+
font=dict(
|
|
248
|
+
size=14,
|
|
249
|
+
color="white"
|
|
250
|
+
),
|
|
251
|
+
bgcolor="black",
|
|
252
|
+
opacity=1.0,
|
|
253
|
+
bordercolor="black",
|
|
254
|
+
borderwidth=0,
|
|
255
|
+
borderpad=4,
|
|
256
|
+
ax=0,
|
|
257
|
+
ay=0
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
annotations.append(
|
|
262
|
+
dict(
|
|
263
|
+
x=x_min + x_padding * 3,
|
|
264
|
+
y=y_max_adjusted - y_range_adjusted * 0.25, # 25% down from the top
|
|
265
|
+
text="High",
|
|
266
|
+
showarrow=False,
|
|
267
|
+
font=dict(
|
|
268
|
+
size=14,
|
|
269
|
+
color="white"
|
|
270
|
+
),
|
|
271
|
+
bgcolor="black",
|
|
272
|
+
opacity=1.0,
|
|
273
|
+
bordercolor="black",
|
|
274
|
+
borderwidth=0,
|
|
275
|
+
borderpad=4,
|
|
276
|
+
ax=-30,
|
|
277
|
+
ay=0,
|
|
278
|
+
textangle=270 # Rotate by 270 degrees
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
annotations.append(
|
|
283
|
+
dict(
|
|
284
|
+
x=x_min + x_padding * 3,
|
|
285
|
+
y=y_max_adjusted - y_range_adjusted * 0.75, # 75% down from the top
|
|
286
|
+
text="Low",
|
|
287
|
+
showarrow=False,
|
|
288
|
+
font=dict(
|
|
289
|
+
size=14,
|
|
290
|
+
color="white"
|
|
291
|
+
),
|
|
292
|
+
bgcolor="black",
|
|
293
|
+
opacity=1.0,
|
|
294
|
+
bordercolor="black",
|
|
295
|
+
borderwidth=0,
|
|
296
|
+
borderpad=4,
|
|
297
|
+
ax=-30,
|
|
298
|
+
ay=0,
|
|
299
|
+
textangle=270 # Rotate by 270 degrees
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# ===== LAYOUT AND STYLING =====
|
|
304
|
+
|
|
305
|
+
# Update layout with annotations
|
|
306
|
+
fig.update_layout(
|
|
307
|
+
yaxis_title='Uncertainty',
|
|
308
|
+
xaxis_title='Sentiment',
|
|
309
|
+
showlegend=False,
|
|
310
|
+
annotations=annotations,
|
|
311
|
+
xaxis=dict(
|
|
312
|
+
range=[x_min - x_padding, x_max + x_padding],
|
|
313
|
+
showticklabels=False,
|
|
314
|
+
showgrid=False,
|
|
315
|
+
zeroline=False,
|
|
316
|
+
showline=False,
|
|
317
|
+
tickwidth=0,
|
|
318
|
+
ticklen=0,
|
|
319
|
+
title=dict(
|
|
320
|
+
text='Sentiment',
|
|
321
|
+
standoff=10,
|
|
322
|
+
font=dict(size=14)
|
|
323
|
+
)
|
|
324
|
+
),
|
|
325
|
+
yaxis=dict(
|
|
326
|
+
range=[y_max_adjusted + y_padding, y_min_adjusted - y_padding], # Flipped y-axis range
|
|
327
|
+
showticklabels=False,
|
|
328
|
+
showgrid=False,
|
|
329
|
+
zeroline=False,
|
|
330
|
+
showline=False,
|
|
331
|
+
tickwidth=0,
|
|
332
|
+
ticklen=0,
|
|
333
|
+
title=dict(
|
|
334
|
+
text='Uncertainty',
|
|
335
|
+
standoff=10,
|
|
336
|
+
font=dict(size=14)
|
|
337
|
+
)
|
|
338
|
+
),
|
|
339
|
+
plot_bgcolor='rgba(0,0,0,0)',
|
|
340
|
+
paper_bgcolor='white',
|
|
341
|
+
margin=dict(l=30, r=30, t=30, b=30),
|
|
342
|
+
width=plot_width,
|
|
343
|
+
height=plot_height
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# ===== GRADIENT BACKGROUND =====
|
|
347
|
+
|
|
348
|
+
# Add a gradient background
|
|
349
|
+
fig.add_shape(
|
|
350
|
+
type="rect",
|
|
351
|
+
x0=x_min - x_padding,
|
|
352
|
+
y0=y_min_adjusted - y_padding,
|
|
353
|
+
x1=x_max + x_padding,
|
|
354
|
+
y1=y_max_adjusted + y_padding,
|
|
355
|
+
fillcolor="rgba(0,0,0,0)",
|
|
356
|
+
line=dict(width=0),
|
|
357
|
+
layer="below"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Add gradient background using multiple rectangles
|
|
361
|
+
num_gradients = 50
|
|
362
|
+
for i in range(num_gradients):
|
|
363
|
+
# Calculate position for this gradient segment
|
|
364
|
+
x_start = (x_min - x_padding) + ((x_max + x_padding) - (x_min - x_padding)) * i / num_gradients
|
|
365
|
+
x_end = (x_min - x_padding) + ((x_max + x_padding) - (x_min - x_padding)) * (i + 1) / num_gradients
|
|
366
|
+
|
|
367
|
+
# Calculate color for this segment (orange to blue)
|
|
368
|
+
r = 1 - i / num_gradients # Red component (1 to 0)
|
|
369
|
+
g = 0.65 - 0.65 * i / num_gradients # Green component (0.65 to 0) - reduced for orange
|
|
370
|
+
b = i / num_gradients # Blue component (0 to 1)
|
|
371
|
+
|
|
372
|
+
fig.add_shape(
|
|
373
|
+
type="rect",
|
|
374
|
+
x0=x_start,
|
|
375
|
+
y0=y_min_adjusted - y_padding,
|
|
376
|
+
x1=x_end,
|
|
377
|
+
y1=y_max_adjusted + y_padding,
|
|
378
|
+
fillcolor=f"rgba({int(r*255)},{int(g*255)},{int(b*255)},0.3)",
|
|
379
|
+
line=dict(width=0),
|
|
380
|
+
layer="below"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Show the plot
|
|
384
|
+
save_figure(fig, "market.png")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from aplotly.plots import plot_benchmark_portfolio_returns
|
|
3
|
+
from aplotly import save_figure
|
|
4
|
+
|
|
5
|
+
# Load your data
|
|
6
|
+
df = pd.read_csv('examples/resources/monthly_report.csv', index_col='month')
|
|
7
|
+
|
|
8
|
+
# Create a plot for positive BTC months with no title
|
|
9
|
+
fig_positive = plot_benchmark_portfolio_returns(
|
|
10
|
+
benchmark_returns=df['BTC/USDT-PERP_return'],
|
|
11
|
+
portfolio_returns=df['Portfolio_return'],
|
|
12
|
+
positive_months=True
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Create a plot for negative BTC months with a custom title
|
|
16
|
+
fig_negative = plot_benchmark_portfolio_returns(
|
|
17
|
+
benchmark_returns=df['BTC/USDT-PERP_return'],
|
|
18
|
+
portfolio_returns=df['Portfolio_return'],
|
|
19
|
+
positive_months=False,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
save_figure(fig_positive, "positive_btc_months.png")
|
|
23
|
+
save_figure(fig_negative, "negative_btc_months.png")
|
|
24
|
+
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aplotly"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.24"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = { file = "LICENSE" }
|
|
10
10
|
classifiers = [
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
requires-python = ">=3.10"
|
|
20
20
|
|
|
21
21
|
[tool.bumpver]
|
|
22
|
-
current_version = "1.1.
|
|
22
|
+
current_version = "1.1.24"
|
|
23
23
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
24
24
|
commit_message = "Bump version {old_version} -> {new_version}"
|
|
25
25
|
commit = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|