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.
Files changed (110) hide show
  1. mainsequence/__init__.py +0 -0
  2. mainsequence/__main__.py +9 -0
  3. mainsequence/cli/__init__.py +1 -0
  4. mainsequence/cli/api.py +157 -0
  5. mainsequence/cli/cli.py +442 -0
  6. mainsequence/cli/config.py +78 -0
  7. mainsequence/cli/ssh_utils.py +126 -0
  8. mainsequence/client/__init__.py +17 -0
  9. mainsequence/client/base.py +431 -0
  10. mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  11. mainsequence/client/data_sources_interfaces/duckdb.py +1468 -0
  12. mainsequence/client/data_sources_interfaces/timescale.py +479 -0
  13. mainsequence/client/models_helpers.py +113 -0
  14. mainsequence/client/models_report_studio.py +412 -0
  15. mainsequence/client/models_tdag.py +2276 -0
  16. mainsequence/client/models_vam.py +1983 -0
  17. mainsequence/client/utils.py +387 -0
  18. mainsequence/dashboards/__init__.py +0 -0
  19. mainsequence/dashboards/streamlit/__init__.py +0 -0
  20. mainsequence/dashboards/streamlit/assets/config.toml +12 -0
  21. mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
  22. mainsequence/dashboards/streamlit/assets/logo.png +0 -0
  23. mainsequence/dashboards/streamlit/core/__init__.py +0 -0
  24. mainsequence/dashboards/streamlit/core/theme.py +212 -0
  25. mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
  26. mainsequence/dashboards/streamlit/scaffold.py +220 -0
  27. mainsequence/instrumentation/__init__.py +7 -0
  28. mainsequence/instrumentation/utils.py +101 -0
  29. mainsequence/instruments/__init__.py +1 -0
  30. mainsequence/instruments/data_interface/__init__.py +10 -0
  31. mainsequence/instruments/data_interface/data_interface.py +361 -0
  32. mainsequence/instruments/instruments/__init__.py +3 -0
  33. mainsequence/instruments/instruments/base_instrument.py +85 -0
  34. mainsequence/instruments/instruments/bond.py +447 -0
  35. mainsequence/instruments/instruments/european_option.py +74 -0
  36. mainsequence/instruments/instruments/interest_rate_swap.py +217 -0
  37. mainsequence/instruments/instruments/json_codec.py +585 -0
  38. mainsequence/instruments/instruments/knockout_fx_option.py +146 -0
  39. mainsequence/instruments/instruments/position.py +475 -0
  40. mainsequence/instruments/instruments/ql_fields.py +239 -0
  41. mainsequence/instruments/instruments/vanilla_fx_option.py +107 -0
  42. mainsequence/instruments/pricing_models/__init__.py +0 -0
  43. mainsequence/instruments/pricing_models/black_scholes.py +49 -0
  44. mainsequence/instruments/pricing_models/bond_pricer.py +182 -0
  45. mainsequence/instruments/pricing_models/fx_option_pricer.py +90 -0
  46. mainsequence/instruments/pricing_models/indices.py +350 -0
  47. mainsequence/instruments/pricing_models/knockout_fx_pricer.py +209 -0
  48. mainsequence/instruments/pricing_models/swap_pricer.py +502 -0
  49. mainsequence/instruments/settings.py +175 -0
  50. mainsequence/instruments/utils.py +29 -0
  51. mainsequence/logconf.py +284 -0
  52. mainsequence/reportbuilder/__init__.py +0 -0
  53. mainsequence/reportbuilder/__main__.py +0 -0
  54. mainsequence/reportbuilder/examples/ms_template_report.py +706 -0
  55. mainsequence/reportbuilder/model.py +713 -0
  56. mainsequence/reportbuilder/slide_templates.py +532 -0
  57. mainsequence/tdag/__init__.py +8 -0
  58. mainsequence/tdag/__main__.py +0 -0
  59. mainsequence/tdag/config.py +129 -0
  60. mainsequence/tdag/data_nodes/__init__.py +12 -0
  61. mainsequence/tdag/data_nodes/build_operations.py +751 -0
  62. mainsequence/tdag/data_nodes/data_nodes.py +1292 -0
  63. mainsequence/tdag/data_nodes/persist_managers.py +812 -0
  64. mainsequence/tdag/data_nodes/run_operations.py +543 -0
  65. mainsequence/tdag/data_nodes/utils.py +24 -0
  66. mainsequence/tdag/future_registry.py +25 -0
  67. mainsequence/tdag/utils.py +40 -0
  68. mainsequence/virtualfundbuilder/__init__.py +45 -0
  69. mainsequence/virtualfundbuilder/__main__.py +235 -0
  70. mainsequence/virtualfundbuilder/agent_interface.py +77 -0
  71. mainsequence/virtualfundbuilder/config_handling.py +86 -0
  72. mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
  73. mainsequence/virtualfundbuilder/contrib/apps/__init__.py +8 -0
  74. mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +164 -0
  75. mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +292 -0
  76. mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +107 -0
  77. mainsequence/virtualfundbuilder/contrib/apps/news_app.py +437 -0
  78. mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +91 -0
  79. mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +95 -0
  80. mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +45 -0
  81. mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +40 -0
  82. mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +147 -0
  83. mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +77 -0
  84. mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +5 -0
  85. mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +61 -0
  86. mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +149 -0
  87. mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +310 -0
  88. mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +78 -0
  89. mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +269 -0
  90. mainsequence/virtualfundbuilder/contrib/prices/__init__.py +1 -0
  91. mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +810 -0
  92. mainsequence/virtualfundbuilder/contrib/prices/utils.py +11 -0
  93. mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +1 -0
  94. mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +313 -0
  95. mainsequence/virtualfundbuilder/data_nodes.py +637 -0
  96. mainsequence/virtualfundbuilder/enums.py +23 -0
  97. mainsequence/virtualfundbuilder/models.py +282 -0
  98. mainsequence/virtualfundbuilder/notebook_handling.py +42 -0
  99. mainsequence/virtualfundbuilder/portfolio_interface.py +272 -0
  100. mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
  101. mainsequence/virtualfundbuilder/resource_factory/app_factory.py +170 -0
  102. mainsequence/virtualfundbuilder/resource_factory/base_factory.py +238 -0
  103. mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +101 -0
  104. mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +183 -0
  105. mainsequence/virtualfundbuilder/utils.py +381 -0
  106. mainsequence-2.0.0.dist-info/METADATA +105 -0
  107. mainsequence-2.0.0.dist-info/RECORD +110 -0
  108. mainsequence-2.0.0.dist-info/WHEEL +5 -0
  109. mainsequence-2.0.0.dist-info/licenses/LICENSE +40 -0
  110. 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
+
@@ -0,0 +1,8 @@
1
+
2
+
3
+ from .config import ogm, configuration, TIME_SERIES_SOURCE_TIMESCALE,RunningMode
4
+ from mainsequence.instrumentation import TracerInstrumentator
5
+ from .data_nodes import DataNode, APIDataNode,WrapperDataNode
6
+
7
+
8
+
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()
@@ -0,0 +1,12 @@
1
+
2
+ from .data_nodes import (DataNode, WrapperDataNode ,
3
+ APIDataNode,)
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+