mainsequence 2.0.0__py3-none-any.whl
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.
- mainsequence/__init__.py +0 -0
- mainsequence/__main__.py +9 -0
- mainsequence/cli/__init__.py +1 -0
- mainsequence/cli/api.py +157 -0
- mainsequence/cli/cli.py +442 -0
- mainsequence/cli/config.py +78 -0
- mainsequence/cli/ssh_utils.py +126 -0
- mainsequence/client/__init__.py +17 -0
- mainsequence/client/base.py +431 -0
- mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- mainsequence/client/data_sources_interfaces/duckdb.py +1468 -0
- mainsequence/client/data_sources_interfaces/timescale.py +479 -0
- mainsequence/client/models_helpers.py +113 -0
- mainsequence/client/models_report_studio.py +412 -0
- mainsequence/client/models_tdag.py +2276 -0
- mainsequence/client/models_vam.py +1983 -0
- mainsequence/client/utils.py +387 -0
- mainsequence/dashboards/__init__.py +0 -0
- mainsequence/dashboards/streamlit/__init__.py +0 -0
- mainsequence/dashboards/streamlit/assets/config.toml +12 -0
- mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- mainsequence/dashboards/streamlit/core/__init__.py +0 -0
- mainsequence/dashboards/streamlit/core/theme.py +212 -0
- mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
- mainsequence/dashboards/streamlit/scaffold.py +220 -0
- mainsequence/instrumentation/__init__.py +7 -0
- mainsequence/instrumentation/utils.py +101 -0
- mainsequence/instruments/__init__.py +1 -0
- mainsequence/instruments/data_interface/__init__.py +10 -0
- mainsequence/instruments/data_interface/data_interface.py +361 -0
- mainsequence/instruments/instruments/__init__.py +3 -0
- mainsequence/instruments/instruments/base_instrument.py +85 -0
- mainsequence/instruments/instruments/bond.py +447 -0
- mainsequence/instruments/instruments/european_option.py +74 -0
- mainsequence/instruments/instruments/interest_rate_swap.py +217 -0
- mainsequence/instruments/instruments/json_codec.py +585 -0
- mainsequence/instruments/instruments/knockout_fx_option.py +146 -0
- mainsequence/instruments/instruments/position.py +475 -0
- mainsequence/instruments/instruments/ql_fields.py +239 -0
- mainsequence/instruments/instruments/vanilla_fx_option.py +107 -0
- mainsequence/instruments/pricing_models/__init__.py +0 -0
- mainsequence/instruments/pricing_models/black_scholes.py +49 -0
- mainsequence/instruments/pricing_models/bond_pricer.py +182 -0
- mainsequence/instruments/pricing_models/fx_option_pricer.py +90 -0
- mainsequence/instruments/pricing_models/indices.py +350 -0
- mainsequence/instruments/pricing_models/knockout_fx_pricer.py +209 -0
- mainsequence/instruments/pricing_models/swap_pricer.py +502 -0
- mainsequence/instruments/settings.py +175 -0
- mainsequence/instruments/utils.py +29 -0
- mainsequence/logconf.py +284 -0
- mainsequence/reportbuilder/__init__.py +0 -0
- mainsequence/reportbuilder/__main__.py +0 -0
- mainsequence/reportbuilder/examples/ms_template_report.py +706 -0
- mainsequence/reportbuilder/model.py +713 -0
- mainsequence/reportbuilder/slide_templates.py +532 -0
- mainsequence/tdag/__init__.py +8 -0
- mainsequence/tdag/__main__.py +0 -0
- mainsequence/tdag/config.py +129 -0
- mainsequence/tdag/data_nodes/__init__.py +12 -0
- mainsequence/tdag/data_nodes/build_operations.py +751 -0
- mainsequence/tdag/data_nodes/data_nodes.py +1292 -0
- mainsequence/tdag/data_nodes/persist_managers.py +812 -0
- mainsequence/tdag/data_nodes/run_operations.py +543 -0
- mainsequence/tdag/data_nodes/utils.py +24 -0
- mainsequence/tdag/future_registry.py +25 -0
- mainsequence/tdag/utils.py +40 -0
- mainsequence/virtualfundbuilder/__init__.py +45 -0
- mainsequence/virtualfundbuilder/__main__.py +235 -0
- mainsequence/virtualfundbuilder/agent_interface.py +77 -0
- mainsequence/virtualfundbuilder/config_handling.py +86 -0
- mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
- mainsequence/virtualfundbuilder/contrib/apps/__init__.py +8 -0
- mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +164 -0
- mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +292 -0
- mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +107 -0
- mainsequence/virtualfundbuilder/contrib/apps/news_app.py +437 -0
- mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +91 -0
- mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +95 -0
- mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +45 -0
- mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +40 -0
- mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +147 -0
- mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +77 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +5 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +61 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +149 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +310 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +78 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +269 -0
- mainsequence/virtualfundbuilder/contrib/prices/__init__.py +1 -0
- mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +810 -0
- mainsequence/virtualfundbuilder/contrib/prices/utils.py +11 -0
- mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +1 -0
- mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +313 -0
- mainsequence/virtualfundbuilder/data_nodes.py +637 -0
- mainsequence/virtualfundbuilder/enums.py +23 -0
- mainsequence/virtualfundbuilder/models.py +282 -0
- mainsequence/virtualfundbuilder/notebook_handling.py +42 -0
- mainsequence/virtualfundbuilder/portfolio_interface.py +272 -0
- mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- mainsequence/virtualfundbuilder/resource_factory/app_factory.py +170 -0
- mainsequence/virtualfundbuilder/resource_factory/base_factory.py +238 -0
- mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +101 -0
- mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +183 -0
- mainsequence/virtualfundbuilder/utils.py +381 -0
- mainsequence-2.0.0.dist-info/METADATA +105 -0
- mainsequence-2.0.0.dist-info/RECORD +110 -0
- mainsequence-2.0.0.dist-info/WHEEL +5 -0
- mainsequence-2.0.0.dist-info/licenses/LICENSE +40 -0
- mainsequence-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,532 @@
|
|
1
|
+
from typing import List, Dict, Any, Optional, Union
|
2
|
+
import plotly.graph_objects as go
|
3
|
+
import plotly.express as px
|
4
|
+
|
5
|
+
from mainsequence.reportbuilder.model import (
|
6
|
+
Slide,
|
7
|
+
GridLayout, GridCell,
|
8
|
+
TextElement, HtmlElement, ImageElement,
|
9
|
+
HorizontalAlign, VerticalAlign, FontWeight,
|
10
|
+
Size, Position, ThemeMode,get_theme_settings
|
11
|
+
)
|
12
|
+
import pandas as pd
|
13
|
+
|
14
|
+
def _transpose_for_plotly(data_rows: List[List[Any]], num_columns: int) -> List[List[Any]]:
|
15
|
+
if not data_rows:
|
16
|
+
return [[] for _ in range(num_columns)]
|
17
|
+
transposed = list(map(list, zip(*data_rows)))
|
18
|
+
return transposed
|
19
|
+
|
20
|
+
|
21
|
+
def generic_plotly_table(
|
22
|
+
headers: List[str],
|
23
|
+
rows: List[List[Any]],
|
24
|
+
table_height: Optional[int] = None, # MODIFIED: Made optional for auto-sizing
|
25
|
+
fig_width: Optional[int] = None,
|
26
|
+
column_widths: Optional[List[Union[int, float]]] = None,
|
27
|
+
cell_align: Union[str, List[str]] = 'left',
|
28
|
+
header_align: str = 'center',
|
29
|
+
cell_font_dict: Optional[Dict[str, Any]] = None,
|
30
|
+
header_font_dict: Optional[Dict[str, Any]] = None,
|
31
|
+
header_fill_color: Optional[str] = None,
|
32
|
+
cell_fill_color: str = '#F5F5F5',
|
33
|
+
line_color: str = '#DCDCDC',
|
34
|
+
header_height: int = 22,
|
35
|
+
cell_height: int = 20,
|
36
|
+
margin_dict: Optional[Dict[str, int]] = None,
|
37
|
+
paper_bgcolor: str = 'rgba(0,0,0,0)',
|
38
|
+
plot_bgcolor: str = 'rgba(0,0,0,0)',
|
39
|
+
responsive: bool = True,
|
40
|
+
display_mode_bar: bool = False,
|
41
|
+
include_plotlyjs: str = False,
|
42
|
+
full_html: bool = False,
|
43
|
+
column_formats: Optional[List[str]] = None,
|
44
|
+
theme_mode: ThemeMode = ThemeMode.light
|
45
|
+
|
46
|
+
) -> str:
|
47
|
+
settings = get_theme_settings(theme_mode)
|
48
|
+
|
49
|
+
effective_margin_dict = margin_dict if margin_dict is not None else dict(l=5, r=5, t=2, b=2)
|
50
|
+
|
51
|
+
if header_fill_color is None:
|
52
|
+
header_fill_color = settings.primary_color
|
53
|
+
if cell_font_dict is None:
|
54
|
+
cell_font_dict = dict(size=12)
|
55
|
+
if header_font_dict is None:
|
56
|
+
header_font_dict = dict(color= settings.background_color, size=14, )
|
57
|
+
|
58
|
+
plotly_column_data = _transpose_for_plotly(rows, len(headers))
|
59
|
+
# Build cell properties, injecting formats if provided
|
60
|
+
cell_props = dict(
|
61
|
+
values=plotly_column_data,
|
62
|
+
fill_color=cell_fill_color,
|
63
|
+
font=cell_font_dict,
|
64
|
+
align=cell_align,
|
65
|
+
line_color=line_color,
|
66
|
+
height=cell_height
|
67
|
+
)
|
68
|
+
if column_formats:
|
69
|
+
cell_props['format'] = column_formats
|
70
|
+
|
71
|
+
fig = go.Figure(data=[go.Table(
|
72
|
+
header=dict(
|
73
|
+
values=headers,
|
74
|
+
fill_color=header_fill_color,
|
75
|
+
font=header_font_dict,
|
76
|
+
align=header_align,
|
77
|
+
line_color=line_color,
|
78
|
+
height=header_height if headers else 0
|
79
|
+
),
|
80
|
+
cells=cell_props,
|
81
|
+
columnwidth=column_widths if column_widths else []
|
82
|
+
)])
|
83
|
+
|
84
|
+
determined_fig_height: int
|
85
|
+
|
86
|
+
if table_height is None:
|
87
|
+
content_actual_height = (header_height if headers else 0) + (len(rows) * cell_height)
|
88
|
+
# Figure height needs to include its own top/bottom margins
|
89
|
+
determined_fig_height = content_actual_height + \
|
90
|
+
effective_margin_dict.get('t', 0) + \
|
91
|
+
effective_margin_dict.get('b', 0) + \
|
92
|
+
4 # Small buffer for any internal Plotly paddings
|
93
|
+
else:
|
94
|
+
determined_fig_height = table_height
|
95
|
+
|
96
|
+
layout_args = {
|
97
|
+
"height": determined_fig_height,
|
98
|
+
"margin": effective_margin_dict,
|
99
|
+
"paper_bgcolor": paper_bgcolor,
|
100
|
+
"plot_bgcolor": plot_bgcolor
|
101
|
+
}
|
102
|
+
if fig_width:
|
103
|
+
layout_args["width"] = fig_width
|
104
|
+
|
105
|
+
fig.update_layout(**layout_args)
|
106
|
+
html = fig.to_html(
|
107
|
+
include_plotlyjs=include_plotlyjs,
|
108
|
+
full_html=full_html,
|
109
|
+
config={'responsive': responsive, 'displayModeBar': display_mode_bar}
|
110
|
+
)
|
111
|
+
html = html.replace('style="height:100%; width:100%;"',
|
112
|
+
'style="width:100%;"') # drop 100 % height
|
113
|
+
return html
|
114
|
+
|
115
|
+
|
116
|
+
def generic_plotly_pie_chart(
|
117
|
+
labels: List[str],
|
118
|
+
values: List[Union[int, float]],
|
119
|
+
height: int = 400,
|
120
|
+
width: int = 450,
|
121
|
+
title: Optional[str] = None,
|
122
|
+
colors: Optional[List[str]] = None,
|
123
|
+
textinfo: str = 'percent+label',
|
124
|
+
textfont_dict: Optional[Dict[str, Any]] = None,
|
125
|
+
hoverinfo: str = 'label+percent+value',
|
126
|
+
showlegend: bool = True,
|
127
|
+
legend_dict: Optional[Dict[str, Any]] = None,
|
128
|
+
margin_dict: Optional[Dict[str, int]] = None,
|
129
|
+
paper_bgcolor: str = 'rgba(0,0,0,0)',
|
130
|
+
plot_bgcolor: str = 'rgba(0,0,0,0)',
|
131
|
+
font_dict: Optional[Dict[str, Any]] = None,
|
132
|
+
sort_traces: bool = False,
|
133
|
+
responsive: bool = True,
|
134
|
+
display_mode_bar: bool = False,
|
135
|
+
include_plotlyjs: str = False,
|
136
|
+
full_html: bool = False,
|
137
|
+
theme_mode: ThemeMode = ThemeMode.light
|
138
|
+
) -> str:
|
139
|
+
|
140
|
+
settings = get_theme_settings(theme_mode)
|
141
|
+
if textfont_dict is None:
|
142
|
+
textfont_dict = dict(size=11)
|
143
|
+
if legend_dict is None:
|
144
|
+
legend_dict = dict(
|
145
|
+
font=dict(size=9, family="Lato, Arial, Helvetica, sans-serif"),
|
146
|
+
orientation="h",
|
147
|
+
yanchor="top",
|
148
|
+
y=-0.1,
|
149
|
+
xanchor="center",
|
150
|
+
x=0.5,
|
151
|
+
bgcolor='rgba(0,0,0,0)'
|
152
|
+
)
|
153
|
+
if margin_dict is None:
|
154
|
+
margin_dict = dict(l=10, r=10, t=10, b=100 if showlegend else 20)
|
155
|
+
if font_dict is None:
|
156
|
+
font_dict = dict(family="Lato, Arial, Helvetica, sans-serif")
|
157
|
+
if colors is None:
|
158
|
+
colors = settings.chart_palette_categorical
|
159
|
+
|
160
|
+
fig = go.Figure(data=[go.Pie(
|
161
|
+
labels=labels,
|
162
|
+
values=values,
|
163
|
+
marker_colors=colors,
|
164
|
+
textinfo=textinfo,
|
165
|
+
textfont=textfont_dict,
|
166
|
+
hoverinfo=hoverinfo,
|
167
|
+
sort=sort_traces,
|
168
|
+
showlegend=showlegend
|
169
|
+
)])
|
170
|
+
|
171
|
+
fig.update_layout(
|
172
|
+
title_text=title,
|
173
|
+
height=height,
|
174
|
+
width=width,
|
175
|
+
margin=margin_dict,
|
176
|
+
paper_bgcolor=paper_bgcolor,
|
177
|
+
plot_bgcolor=plot_bgcolor,
|
178
|
+
font=font_dict,
|
179
|
+
legend=legend_dict
|
180
|
+
)
|
181
|
+
return fig.to_html(
|
182
|
+
include_plotlyjs=include_plotlyjs,
|
183
|
+
full_html=full_html,
|
184
|
+
config={'responsive': responsive, 'displayModeBar': display_mode_bar}
|
185
|
+
)
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
|
190
|
+
def generic_plotly_bar_chart(
|
191
|
+
y_values: List[Union[str, int, float]],
|
192
|
+
x_values: List[Union[int, float]],
|
193
|
+
orientation: str="h",
|
194
|
+
height: int = 400,
|
195
|
+
width: int = 450,
|
196
|
+
title: Optional[str] = None,
|
197
|
+
bar_color: Union[str, List[str]] = None,
|
198
|
+
text_template: Optional[str] = None,
|
199
|
+
textposition: str = 'outside',
|
200
|
+
textfont_dict: Optional[Dict[str, Any]] = None,
|
201
|
+
hoverinfo: str = 'x+y', # Default, will be adapted by plotly if orientation changes
|
202
|
+
margin_dict: Optional[Dict[str, int]] = None,
|
203
|
+
paper_bgcolor: str = 'rgba(0,0,0,0)',
|
204
|
+
plot_bgcolor: str = 'rgba(0,0,0,0)',
|
205
|
+
xaxis_dict: Optional[Dict[str, Any]] = None,
|
206
|
+
yaxis_dict: Optional[Dict[str, Any]] = None,
|
207
|
+
bargap: float = 0.2, # Slightly reduced default bargap for a tighter look
|
208
|
+
font_dict: Optional[Dict[str, Any]] = None,
|
209
|
+
responsive: bool = True,
|
210
|
+
display_mode_bar: bool = False,
|
211
|
+
include_plotlyjs: bool = False,
|
212
|
+
full_html: bool = False,
|
213
|
+
theme_mode: ThemeMode = ThemeMode.light
|
214
|
+
|
215
|
+
) -> str:
|
216
|
+
# 1) theme & defaults
|
217
|
+
settings = get_theme_settings(theme_mode)
|
218
|
+
if bar_color is None:
|
219
|
+
bar_color = settings.chart_palette_categorical
|
220
|
+
if textfont_dict is None:
|
221
|
+
textfont_dict = dict(size=9)
|
222
|
+
if margin_dict is None:
|
223
|
+
# extra left margin for horizontal labels
|
224
|
+
margin_dict = dict(l=150 if orientation == 'h' else 40, r=20, t=5, b=20)
|
225
|
+
if font_dict is None:
|
226
|
+
font_dict = dict(family="Lato, Arial, Helvetica, sans-serif")
|
227
|
+
|
228
|
+
# 2) axis defaults (no autorange reversal)
|
229
|
+
default_axis = dict(
|
230
|
+
showgrid=False,
|
231
|
+
zeroline=False,
|
232
|
+
showline=False,
|
233
|
+
tickfont=dict(size=9, color="#333333")
|
234
|
+
)
|
235
|
+
xaxis_cfg = xaxis_dict.copy() if xaxis_dict else default_axis.copy()
|
236
|
+
yaxis_cfg = yaxis_dict.copy() if yaxis_dict else default_axis.copy()
|
237
|
+
|
238
|
+
# 3) build data_params correctly
|
239
|
+
if orientation == 'h':
|
240
|
+
# horizontal: numeric→x, categories→y
|
241
|
+
data_params = dict(x=x_values, y=y_values)
|
242
|
+
if text_template is None:
|
243
|
+
text_template = "%{x:.2f}"
|
244
|
+
elif orientation == 'v':
|
245
|
+
# vertical: categories→x, numeric→y
|
246
|
+
data_params = dict(x=y_values, y=x_values)
|
247
|
+
if text_template is None:
|
248
|
+
text_template = "%{y:.2f}"
|
249
|
+
else:
|
250
|
+
raise ValueError("Orientation must be 'h' or 'v'")
|
251
|
+
|
252
|
+
# 4) build the figure
|
253
|
+
fig = go.Figure(go.Bar(
|
254
|
+
**data_params,
|
255
|
+
orientation=orientation,
|
256
|
+
marker_color=bar_color,
|
257
|
+
texttemplate=text_template,
|
258
|
+
textposition=textposition,
|
259
|
+
textfont=textfont_dict,
|
260
|
+
hoverinfo=hoverinfo
|
261
|
+
))
|
262
|
+
|
263
|
+
fig.update_layout(
|
264
|
+
title_text=title,
|
265
|
+
height=height,
|
266
|
+
width=width,
|
267
|
+
margin=margin_dict,
|
268
|
+
paper_bgcolor=paper_bgcolor,
|
269
|
+
plot_bgcolor=plot_bgcolor,
|
270
|
+
xaxis=xaxis_cfg,
|
271
|
+
yaxis=yaxis_cfg,
|
272
|
+
bargap=bargap,
|
273
|
+
font=font_dict
|
274
|
+
)
|
275
|
+
return fig.to_html(
|
276
|
+
include_plotlyjs=include_plotlyjs,
|
277
|
+
full_html=full_html,
|
278
|
+
config={'responsive': responsive, 'displayModeBar': display_mode_bar}
|
279
|
+
)
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
|
284
|
+
def generic_plotly_grouped_bar_chart(
|
285
|
+
x_values: List[str],
|
286
|
+
series_data: List[Dict[str, Any]],
|
287
|
+
height: int,
|
288
|
+
chart_title: str = "",
|
289
|
+
width: Optional[int] = None,
|
290
|
+
y_axis_tick_format: Optional[str] = ".2f",
|
291
|
+
bar_text_template: Optional[str] = "%{y:.2f}",
|
292
|
+
bar_text_position: str = "outside",
|
293
|
+
bar_text_font_size_factor: float = 1.0,
|
294
|
+
barmode: str = "group",
|
295
|
+
legend_dict: Optional[Dict[str, Any]] = None,
|
296
|
+
margin_dict: Optional[Dict[str, int]] = None,
|
297
|
+
title_x_position: float = 0.05,
|
298
|
+
xaxis_tickangle: Optional[float] = None,
|
299
|
+
paper_bgcolor: str = 'rgba(0,0,0,0)',
|
300
|
+
plot_bgcolor: str = 'rgba(0,0,0,0)',
|
301
|
+
include_plotlyjs: bool = False,
|
302
|
+
full_html: bool = False,
|
303
|
+
display_mode_bar: bool = False,
|
304
|
+
responsive: bool = True,
|
305
|
+
theme_mode:ThemeMode =ThemeMode.light
|
306
|
+
|
307
|
+
) -> str:
|
308
|
+
fig = go.Figure()
|
309
|
+
styles = get_theme_settings(theme_mode)
|
310
|
+
|
311
|
+
|
312
|
+
for counter,series in enumerate(series_data):
|
313
|
+
marker_color = series.get('color', None)
|
314
|
+
if marker_color is None:
|
315
|
+
marker_color = styles.chart_palette_categorical[counter%len(styles.chart_palette_categorical)]
|
316
|
+
trace = go.Bar(
|
317
|
+
name=series['name'],
|
318
|
+
x=x_values,
|
319
|
+
y=series['y_values'],
|
320
|
+
marker_color=marker_color
|
321
|
+
)
|
322
|
+
if bar_text_template:
|
323
|
+
trace.texttemplate = bar_text_template
|
324
|
+
trace.textposition = bar_text_position
|
325
|
+
trace.textfont = dict(
|
326
|
+
size=int(styles.chart_label_font_size * bar_text_font_size_factor),
|
327
|
+
family=styles.font_family_paragraphs,
|
328
|
+
color=styles.paragraph_color
|
329
|
+
)
|
330
|
+
fig.add_trace(trace)
|
331
|
+
|
332
|
+
all_y = [y for series in series_data for y in series['y_values']]
|
333
|
+
y_max = max(all_y) * 1.1 # 10% above the tallest bar
|
334
|
+
y_min = min(all_y) * 1.1 if min(all_y)<0 else min(all_y)*.9
|
335
|
+
|
336
|
+
default_legend_config = dict(
|
337
|
+
font=dict(size=styles.chart_label_font_size, family=styles.font_family_paragraphs),
|
338
|
+
orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1,
|
339
|
+
bgcolor='rgba(0,0,0,0)'
|
340
|
+
)
|
341
|
+
if legend_dict is not None:
|
342
|
+
default_legend_config.update(legend_dict)
|
343
|
+
|
344
|
+
final_margin_dict = margin_dict if margin_dict is not None else dict(l=40, r=20, t=50 if chart_title else 50, b=50)
|
345
|
+
if xaxis_tickangle is not None and xaxis_tickangle != 0:
|
346
|
+
final_margin_dict["b"] = max(final_margin_dict.get("b", 30), 70 + abs(xaxis_tickangle) // 10 * 5)
|
347
|
+
|
348
|
+
fig.update_layout(
|
349
|
+
title_text=chart_title,
|
350
|
+
title_font=dict(size=styles.font_size_h4, family=styles.font_family_paragraphs, color=styles.heading_color),
|
351
|
+
title_x=title_x_position,
|
352
|
+
height=height,
|
353
|
+
width=width,
|
354
|
+
barmode=barmode,
|
355
|
+
xaxis_tickfont_size=styles.chart_label_font_size,
|
356
|
+
yaxis_tickfont_size=styles.chart_label_font_size,
|
357
|
+
yaxis_tickformat=y_axis_tick_format,
|
358
|
+
yaxis=dict(range=[y_min, y_max]), # <-- set min/max here
|
359
|
+
legend=default_legend_config,
|
360
|
+
margin=final_margin_dict,
|
361
|
+
paper_bgcolor=paper_bgcolor,
|
362
|
+
plot_bgcolor=plot_bgcolor,
|
363
|
+
font=dict(family=styles.font_family_paragraphs)
|
364
|
+
)
|
365
|
+
|
366
|
+
if xaxis_tickangle is not None:
|
367
|
+
fig.update_xaxes(tickangle=xaxis_tickangle)
|
368
|
+
|
369
|
+
return fig.to_html(
|
370
|
+
include_plotlyjs=include_plotlyjs,
|
371
|
+
full_html=full_html,
|
372
|
+
config={'responsive': responsive, 'displayModeBar': display_mode_bar}
|
373
|
+
)
|
374
|
+
|
375
|
+
|
376
|
+
def _build_traces(
|
377
|
+
x_values: List,
|
378
|
+
series_data: List[Dict[str, Any]],
|
379
|
+
styles: Any
|
380
|
+
) -> List[go.Scatter]:
|
381
|
+
traces = []
|
382
|
+
palette = styles.chart_palette_categorical
|
383
|
+
for i, series in enumerate(series_data):
|
384
|
+
traces.append(go.Scatter(
|
385
|
+
name=series.get('name', f'Series {i + 1}'),
|
386
|
+
x=x_values,
|
387
|
+
y=series['y_values'],
|
388
|
+
mode='lines',
|
389
|
+
line=dict(
|
390
|
+
color=series.get('color', palette[i % len(palette)]),
|
391
|
+
width=2
|
392
|
+
),
|
393
|
+
hoverinfo='x+y+name'
|
394
|
+
))
|
395
|
+
return traces
|
396
|
+
|
397
|
+
def _build_layout(
|
398
|
+
styles: Any,
|
399
|
+
chart_title: str,
|
400
|
+
y_axis_title: str,
|
401
|
+
y_axis_tick_format: Optional[str],
|
402
|
+
legend_dict: Optional[Dict[str, Any]],
|
403
|
+
margin_dict: Optional[Dict[str, int]],
|
404
|
+
theme_mode: Any,
|
405
|
+
height: Optional[int],
|
406
|
+
width: Optional[int],
|
407
|
+
) -> Dict[str, Any]:
|
408
|
+
# legend
|
409
|
+
default_legend = dict(
|
410
|
+
font=dict(size=styles.chart_label_font_size, family=styles.font_family_paragraphs),
|
411
|
+
orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1,
|
412
|
+
bgcolor='rgba(0,0,0,0)'
|
413
|
+
)
|
414
|
+
if legend_dict:
|
415
|
+
default_legend.update(legend_dict)
|
416
|
+
|
417
|
+
# margin
|
418
|
+
final_margin = margin_dict or dict(
|
419
|
+
l=60, r=40,
|
420
|
+
t=60 if chart_title else 20,
|
421
|
+
b=50
|
422
|
+
)
|
423
|
+
|
424
|
+
layout = dict(
|
425
|
+
title_text=chart_title,
|
426
|
+
title_font=dict(
|
427
|
+
size=styles.font_size_h4,
|
428
|
+
family=styles.font_family_headings,
|
429
|
+
color=styles.heading_color
|
430
|
+
),
|
431
|
+
title_x=0.5,
|
432
|
+
legend=default_legend,
|
433
|
+
margin=final_margin,
|
434
|
+
paper_bgcolor='rgba(0,0,0,0)',
|
435
|
+
plot_bgcolor='rgba(0,0,0,0)',
|
436
|
+
font=dict(
|
437
|
+
family=styles.font_family_paragraphs,
|
438
|
+
color=styles.paragraph_color
|
439
|
+
),
|
440
|
+
xaxis=dict(
|
441
|
+
showgrid=True,
|
442
|
+
gridcolor=styles.light_paragraph_color if theme_mode == ThemeMode.light else "#444",
|
443
|
+
gridwidth=0.5,
|
444
|
+
zeroline=False
|
445
|
+
),
|
446
|
+
yaxis=dict(
|
447
|
+
title=y_axis_title,
|
448
|
+
tickformat=y_axis_tick_format,
|
449
|
+
showgrid=True,
|
450
|
+
gridcolor=styles.light_paragraph_color if theme_mode == ThemeMode.light else "#444",
|
451
|
+
gridwidth=0.5,
|
452
|
+
zeroline=False
|
453
|
+
)
|
454
|
+
)
|
455
|
+
if height:
|
456
|
+
layout["height"] = height
|
457
|
+
if width:
|
458
|
+
layout["width"] = width
|
459
|
+
|
460
|
+
return layout
|
461
|
+
|
462
|
+
def generic_plotly_line_chart(
|
463
|
+
x_values: List,
|
464
|
+
series_data: List[Dict[str, Any]],
|
465
|
+
# height and width are now optional for autosizing
|
466
|
+
height: Optional[int] = None,
|
467
|
+
width: Optional[int] = None,
|
468
|
+
chart_title: str = "",
|
469
|
+
y_axis_title: str = "",
|
470
|
+
y_axis_tick_format: Optional[str] = ".2f",
|
471
|
+
legend_dict: Optional[Dict[str, Any]] = None,
|
472
|
+
margin_dict: Optional[Dict[str, int]] = None,
|
473
|
+
theme_mode: ThemeMode = ThemeMode.light,
|
474
|
+
include_plotlyjs: str = "cdn",
|
475
|
+
full_html: bool = True,
|
476
|
+
display_mode_bar: bool = False,
|
477
|
+
responsive: bool = True
|
478
|
+
) -> str:
|
479
|
+
"""
|
480
|
+
Responsive multi-series line chart.
|
481
|
+
"""
|
482
|
+
styles = get_theme_settings(theme_mode)
|
483
|
+
fig = go.Figure()
|
484
|
+
|
485
|
+
# add traces
|
486
|
+
for trace in _build_traces(x_values, series_data, styles):
|
487
|
+
fig.add_trace(trace)
|
488
|
+
|
489
|
+
# apply layout
|
490
|
+
layout_args = _build_layout(
|
491
|
+
styles, chart_title, y_axis_title, y_axis_tick_format,
|
492
|
+
legend_dict, margin_dict, theme_mode, height, width
|
493
|
+
)
|
494
|
+
fig.update_layout(**layout_args)
|
495
|
+
|
496
|
+
return fig.to_html(
|
497
|
+
include_plotlyjs=include_plotlyjs,
|
498
|
+
full_html=full_html,
|
499
|
+
config={'responsive': responsive, 'displayModeBar': display_mode_bar}
|
500
|
+
)
|
501
|
+
|
502
|
+
def plot_dataframe_line_chart(
|
503
|
+
df: pd.DataFrame,
|
504
|
+
x_column: Optional[str]=None,
|
505
|
+
columns: Optional[List[str]] = None,
|
506
|
+
**plot_kwargs
|
507
|
+
) -> str:
|
508
|
+
"""
|
509
|
+
Plot all specified columns from df against the x_column.
|
510
|
+
Any generic_plotly_line_chart kwargs can be passed through plot_kwargs.
|
511
|
+
"""
|
512
|
+
index_name=df.index.name
|
513
|
+
df=df.reset_index()
|
514
|
+
x_column = x_column or index_name
|
515
|
+
columns=columns or [c for c in df.columns if c!=index_name]
|
516
|
+
series_data = [
|
517
|
+
{
|
518
|
+
'name': col,
|
519
|
+
'y_values': df[col].tolist(),
|
520
|
+
# optionally add 'color': ... here if you want fixed colors per column
|
521
|
+
}
|
522
|
+
for col in columns
|
523
|
+
]
|
524
|
+
x_values = df[x_column].tolist()
|
525
|
+
# delegate to generic
|
526
|
+
return generic_plotly_line_chart(
|
527
|
+
x_values=x_values,
|
528
|
+
series_data=series_data,
|
529
|
+
**plot_kwargs
|
530
|
+
)
|
531
|
+
|
532
|
+
|
File without changes
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import os
|
2
|
+
from pathlib import Path
|
3
|
+
from enum import Enum
|
4
|
+
from .utils import read_key_from_yaml, write_yaml, read_yaml
|
5
|
+
|
6
|
+
DEFAULT_RETENTION_POLICY = dict(scheduler_name="default", retention_policy_time="90 days")
|
7
|
+
|
8
|
+
API_TS_PICKLE_PREFIFX = "api-"
|
9
|
+
|
10
|
+
TIME_SERIES_SOURCE_TIMESCALE = "timescale"
|
11
|
+
TIME_SERIES_SOURCE_PARQUET = "parquet"
|
12
|
+
|
13
|
+
TDAG_PATH = os.environ.get("TDAG_ROOT_PATH", f"{str(Path.home())}/tdag")
|
14
|
+
TDAG_CONFIG_PATH = os.environ.get("TDAG_CONFIG_PATH", f"{TDAG_PATH}/config.yml")
|
15
|
+
|
16
|
+
TDAG_DATA_PATH = f"{TDAG_PATH}/data"
|
17
|
+
GT_TEMP_PATH = f"{TDAG_PATH}/temp"
|
18
|
+
GT_RAY_FOLDER = f"{TDAG_PATH}/ray"
|
19
|
+
|
20
|
+
TIME_SERIES_FOLDER = f"{TDAG_DATA_PATH}/time_series_data"
|
21
|
+
os.makedirs(TIME_SERIES_FOLDER, exist_ok=True)
|
22
|
+
Path(GT_TEMP_PATH).mkdir(parents=True, exist_ok=True)
|
23
|
+
Path(GT_RAY_FOLDER).mkdir(parents=True, exist_ok=True)
|
24
|
+
|
25
|
+
dir_path = os.path.dirname(os.path.realpath(__file__))
|
26
|
+
|
27
|
+
class bcolors:
|
28
|
+
HEADER = '\033[95m'
|
29
|
+
OKBLUE = '\033[94m'
|
30
|
+
OKCYAN = '\033[96m'
|
31
|
+
OKGREEN = '\033[92m'
|
32
|
+
IMPORTANT = '\033[45m'
|
33
|
+
WARNING = '\033[93m'
|
34
|
+
FAIL = '\033[91m'
|
35
|
+
ENDC = '\033[0m'
|
36
|
+
BOLD = '\033[1m'
|
37
|
+
UNDERLINE = '\033[4m'
|
38
|
+
|
39
|
+
class RunningMode(Enum):
|
40
|
+
TRAINING = "train"
|
41
|
+
LIVE = "live"
|
42
|
+
|
43
|
+
class Configuration:
|
44
|
+
OBLIGATORY_ENV_VARIABLES = [
|
45
|
+
"TDAG_ENDPOINT",
|
46
|
+
"MAINSEQUENCE_TOKEN",
|
47
|
+
]
|
48
|
+
|
49
|
+
def __init__(self):
|
50
|
+
self.set_gt_configuration()
|
51
|
+
self._assert_env_variables()
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def add_env_variables_to_registry(cls,env_vars:list):
|
55
|
+
cls.OBLIGATORY_ENV_VARIABLES.extend(env_vars)
|
56
|
+
|
57
|
+
def set_gt_configuration(self):
|
58
|
+
if not os.path.isfile(TDAG_CONFIG_PATH):
|
59
|
+
self._build_template_yaml()
|
60
|
+
|
61
|
+
self.configuration = read_yaml(TDAG_CONFIG_PATH)
|
62
|
+
|
63
|
+
def _assert_env_variables(self):
|
64
|
+
do_not_check= os.environ.get("DO_NOT_CHECK_TDAG","false").lower()=="true"
|
65
|
+
if do_not_check== True:
|
66
|
+
return None
|
67
|
+
for ob_var in self.OBLIGATORY_ENV_VARIABLES:
|
68
|
+
assert ob_var in os.environ, f"{ob_var} not in environment variables"
|
69
|
+
|
70
|
+
def _build_template_yaml(self):
|
71
|
+
config = {
|
72
|
+
"time_series_config": {
|
73
|
+
"ignore_update_timeout": False,
|
74
|
+
|
75
|
+
},
|
76
|
+
"instrumentation_config": {
|
77
|
+
"grafana_agent_host": "localhost",
|
78
|
+
"export_trace_to_console": False
|
79
|
+
}
|
80
|
+
}
|
81
|
+
write_yaml(path=TDAG_CONFIG_PATH, dict_file=config)
|
82
|
+
|
83
|
+
configuration = Configuration()
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
class TimeSeriesOGM:
|
89
|
+
def __init__(self):
|
90
|
+
os.makedirs(self.time_series_config["LOCAL_DATA_PATH"], exist_ok=True)
|
91
|
+
|
92
|
+
@property
|
93
|
+
def time_series_config(self):
|
94
|
+
ts_config = read_key_from_yaml("time_series_config", path=TDAG_CONFIG_PATH)
|
95
|
+
ts_config["LOCAL_DATA_PATH"] = TIME_SERIES_FOLDER
|
96
|
+
return ts_config
|
97
|
+
|
98
|
+
def verify_exist(self, target_path):
|
99
|
+
os.makedirs(target_path, exist_ok=True)
|
100
|
+
|
101
|
+
@property
|
102
|
+
def time_series_folder(self):
|
103
|
+
target_path = self.time_series_config["LOCAL_DATA_PATH"]
|
104
|
+
self.verify_exist(target_path=target_path)
|
105
|
+
return target_path
|
106
|
+
|
107
|
+
|
108
|
+
@property
|
109
|
+
def temp_folder(self):
|
110
|
+
target_path = os.path.join(f"{self.time_series_folder}", "temp")
|
111
|
+
self.verify_exist(target_path=target_path)
|
112
|
+
return target_path
|
113
|
+
|
114
|
+
@property
|
115
|
+
def local_metadata_path(self):
|
116
|
+
target_path = os.path.join(f"{self.time_series_folder}", "metadata")
|
117
|
+
self.verify_exist(target_path=target_path)
|
118
|
+
return target_path
|
119
|
+
|
120
|
+
@property
|
121
|
+
def pickle_storage_path(self):
|
122
|
+
target_path = os.path.join(f"{self.time_series_folder}", "pickled_ts")
|
123
|
+
self.verify_exist(target_path=target_path)
|
124
|
+
return target_path
|
125
|
+
|
126
|
+
def get_ts_pickle_path(self, update_hash: str):
|
127
|
+
return os.path.join(f"{self.pickle_storage_path}", f"{update_hash}.pickle")
|
128
|
+
|
129
|
+
ogm = TimeSeriesOGM()
|