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.
Files changed (38) hide show
  1. {aplotly-1.1.22/aplotly.egg-info → aplotly-1.1.24}/PKG-INFO +1 -1
  2. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/colors.py +28 -1
  3. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/plots.py +139 -1
  4. {aplotly-1.1.22 → aplotly-1.1.24/aplotly.egg-info}/PKG-INFO +1 -1
  5. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/SOURCES.txt +3 -0
  6. aplotly-1.1.24/examples/fetch_deribit_data.py +51 -0
  7. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_lines.py +4 -3
  8. aplotly-1.1.24/examples/plot_market.py +384 -0
  9. aplotly-1.1.24/examples/plot_return_side.py +24 -0
  10. {aplotly-1.1.22 → aplotly-1.1.24}/pyproject.toml +2 -2
  11. {aplotly-1.1.22 → aplotly-1.1.24}/LICENSE +0 -0
  12. {aplotly-1.1.22 → aplotly-1.1.24}/MANIFEST.in +0 -0
  13. {aplotly-1.1.22 → aplotly-1.1.24}/README.md +0 -0
  14. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/__init__.py +0 -0
  15. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/io.py +0 -0
  16. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/style.py +0 -0
  17. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly/utils/return_breakdown.py +0 -0
  18. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/dependency_links.txt +0 -0
  19. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/requires.txt +0 -0
  20. {aplotly-1.1.22 → aplotly-1.1.24}/aplotly.egg-info/top_level.txt +0 -0
  21. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_bars.py +0 -0
  22. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_confidence_interval.py +0 -0
  23. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_correlation.py +0 -0
  24. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_exposure_tree.py +0 -0
  25. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_gauge.py +0 -0
  26. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_line.py +0 -0
  27. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_line_and_save.py +0 -0
  28. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_lines_with_dist.py +0 -0
  29. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_multiple_performance.py +0 -0
  30. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_participation.py +0 -0
  31. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_performance.py +0 -0
  32. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_performance_by_trade.py +0 -0
  33. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_returns_breakdown.py +0 -0
  34. {aplotly-1.1.22 → aplotly-1.1.24}/examples/plot_returns_tree.py +0 -0
  35. {aplotly-1.1.22 → aplotly-1.1.24}/setup.cfg +0 -0
  36. {aplotly-1.1.22 → aplotly-1.1.24}/tests/test_colors.py +0 -0
  37. {aplotly-1.1.22 → aplotly-1.1.24}/tests/test_io.py +0 -0
  38. {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.22
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.22
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(3)],
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.22"
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"
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