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,412 @@
1
+
2
+ from .base import BasePydanticModel, BaseObjectOrm, TDAG_ENDPOINT
3
+ from .utils import (is_process_running, get_network_ip,
4
+ TDAG_CONSTANTS,
5
+ DATE_FORMAT, AuthLoaders, make_request, set_types_in_table, request_to_datetime, serialize_to_json, bios_uuid)
6
+ from typing import Optional, List,Annotated, Union, Literal
7
+ from pydantic import constr, Field,conint, BaseModel
8
+ import datetime
9
+
10
+
11
+ # Define a reusable HexColor annotated type
12
+ HexColor = Annotated[
13
+ str,
14
+ Field(
15
+ pattern=r"^#[0-9A-Fa-f]{6}$",
16
+ description="HEX color in format #RRGGBB",
17
+ min_length=7,
18
+ max_length=7,
19
+ )
20
+ ]
21
+
22
+
23
+ class Theme(BasePydanticModel,BaseObjectOrm):
24
+ id: int
25
+ theme_type: constr(max_length=15) = Field(
26
+ ..., description="‘standard’ or ‘custom’"
27
+ )
28
+ name: constr(max_length=100)
29
+ created: datetime.datetime
30
+ updated: datetime.datetime
31
+
32
+ mode: constr(max_length=5) = Field(
33
+ ..., description="‘light’ or ‘dark’"
34
+ )
35
+ editor_background: Optional[dict] = Field(
36
+ None, description="FK to Background.id"
37
+ )
38
+
39
+ font_family_headings: Optional[constr(max_length=100)] = Field(
40
+ "", description="--font-family-headings"
41
+ )
42
+ font_family_paragraphs: Optional[constr(max_length=100)] = Field(
43
+ "", description="--font-family-paragraphs"
44
+ )
45
+
46
+ font_size_h1: conint(ge=1) = Field(48, description="--font-size-h1 (px)")
47
+ font_size_h2: conint(ge=1) = Field(40, description="--font-size-h2 (px)")
48
+ font_size_h3: conint(ge=1) = Field(32, description="--font-size-h3 (px)")
49
+ font_size_h4: conint(ge=1) = Field(24, description="--font-size-h4 (px)")
50
+ font_size_h5: conint(ge=1) = Field(20, description="--font-size-h5 (px)")
51
+ font_size_h6: conint(ge=1) = Field(16, description="--font-size-h6 (px)")
52
+ font_size_p: conint(ge=1) = Field(14, description="--font-size-p (px)")
53
+
54
+ primary_color: HexColor = Field("#0d6efd")
55
+ secondary_color: HexColor = Field("#6c757d")
56
+ accent_color_1: HexColor= Field("#198754")
57
+ accent_color_2: HexColor = Field("#ffc107")
58
+ heading_color: HexColor = Field("#212529")
59
+ paragraph_color: HexColor= Field("#495057")
60
+ light_paragraph_color: HexColor = Field("#6c757d")
61
+ background_color: HexColor = Field("#ffffff")
62
+
63
+ title_column_width: constr(max_length=15) = Field(
64
+ "150px", description="--title-column-width"
65
+ )
66
+ chart_label_font_size: conint(ge=1) = Field(
67
+ 12, description="--chart-label-font-size (px)"
68
+ )
69
+
70
+ chart_palette_sequential: List[HexColor] = Field(
71
+ default_factory=list,
72
+ description="List of 5 HEX colours for sequential palettes",
73
+ )
74
+ chart_palette_diverging: List[HexColor] = Field(
75
+ default_factory=list,
76
+ description="List of 5 HEX colours for diverging palettes",
77
+ )
78
+ chart_palette_categorical: List[HexColor] = Field(
79
+ default_factory=list,
80
+ description="List of 6 HEX colours for categorical palettes",
81
+ )
82
+
83
+ def set_plotly_theme(self):
84
+ import plotly.io as pio
85
+ import plotly.graph_objects as go
86
+ try:
87
+ import plotly.express as px
88
+ except ImportError:
89
+ px = None
90
+
91
+ # --------------------- derive colours and fonts --------------------------
92
+ base_font = dict(
93
+ family=self.font_family_paragraphs or "Inter, Arial, sans-serif",
94
+ size=self.font_size_p,
95
+ color=self.paragraph_color,
96
+ )
97
+ title_font = dict(
98
+ family=self.font_family_headings or base_font["family"],
99
+ size=self.font_size_h4,
100
+ color=self.heading_color,
101
+ )
102
+
103
+ # categorical palette
104
+ colorway = (
105
+ self.chart_palette_categorical[:]
106
+ if len(self.chart_palette_categorical) >= 3
107
+ else [
108
+ self.primary_color,
109
+ self.secondary_color,
110
+ self.accent_color_1,
111
+ self.accent_color_2,
112
+ self.heading_color,
113
+ self.paragraph_color,
114
+ ]
115
+ )
116
+
117
+ sequential = (
118
+ self.chart_palette_sequential[:]
119
+ if len(self.chart_palette_sequential) >= 3
120
+ else colorway
121
+ )
122
+ diverging = (
123
+ self.chart_palette_diverging[:]
124
+ if len(self.chart_palette_diverging) >= 3
125
+ else colorway[::-1]
126
+ )
127
+
128
+ paper_bg = plot_bg = self.background_color
129
+ if self.mode.lower() == "dark" and paper_bg.lower() == "#ffffff":
130
+ paper_bg = plot_bg = "#111111"
131
+
132
+ # --------------------------- build template ------------------------------
133
+ template = go.layout.Template(
134
+ layout=dict(
135
+ font=base_font,
136
+ title_font=title_font,
137
+ paper_bgcolor=paper_bg,
138
+ plot_bgcolor=plot_bg,
139
+ colorway=colorway,
140
+ # ↓↓↓ **correct spot for continuous scales** ↓↓↓
141
+ colorscale=dict(
142
+ sequential=sequential,
143
+ diverging=diverging,
144
+ ),
145
+ xaxis=dict(showgrid=False, zeroline=False),
146
+ yaxis=dict(showgrid=True, zeroline=False),
147
+ )
148
+ )
149
+
150
+ # ---------------------- register & activate ------------------------------
151
+ tpl_name = f"theme_{self.id}_{self.name}"
152
+ pio.templates[tpl_name] = template
153
+ pio.templates.default = tpl_name
154
+ if px:
155
+ px.defaults.template = tpl_name
156
+ px.defaults.color_discrete_sequence = colorway
157
+ px.defaults.color_continuous_scale = sequential
158
+
159
+
160
+
161
+ class Folder(BasePydanticModel, BaseObjectOrm):
162
+ id:Optional[int]=None
163
+ name:str
164
+ slug:str
165
+
166
+
167
+ @classmethod
168
+ def get_or_create(cls, *args, **kwargs):
169
+ url = f"{cls.get_object_url()}/get-or-create/"
170
+ payload = {"json": kwargs}
171
+ r = make_request(
172
+ s=cls.build_session(),
173
+ loaders=cls.LOADERS,
174
+ r_type="POST",
175
+ url=url,
176
+ payload=payload
177
+ )
178
+ if r.status_code not in [200, 201]:
179
+ raise Exception(f"Error appending creating: {r.text}")
180
+ # Return a new instance of AssetCategory built from the response JSON.
181
+ return cls(**r.json())
182
+
183
+ class Slide(BasePydanticModel,BaseObjectOrm):
184
+ id:Optional[int]=None
185
+ number: int = Field(
186
+ ...,
187
+ ge=0,
188
+ description="Number of the slide in presentation order"
189
+ )
190
+ body: str = Field(
191
+ ...,
192
+ description=(
193
+ "Tiptap rich-text document for the main content, serialized as JSON. "
194
+ "Must follow the Tiptap ‘doc’ schema, e.g.: "
195
+ '{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null}}]}'
196
+ ),
197
+ example='{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null}}]}'
198
+ )
199
+ header: Optional[str] = Field(
200
+ None,
201
+ description=(
202
+ "Optional Tiptap rich-text document for the header, serialized as JSON. "
203
+ "If present, should follow the Tiptap ‘doc’ schema, e.g.: "
204
+ '{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null}}]}'
205
+ ),
206
+ example='{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null}}]}'
207
+ )
208
+ footer: Optional[str] = Field(
209
+ None,
210
+ description=(
211
+ "Optional Tiptap rich-text document for the footer, serialized as JSON. "
212
+ "If present, should follow the Tiptap ‘doc’ schema, e.g.: "
213
+ '{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null}}]}'
214
+ ),
215
+ example='{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null}}]}'
216
+ )
217
+ created_at: datetime.datetime = Field(
218
+ default_factory=datetime.datetime.utcnow,
219
+ description="Timestamp when the record was created"
220
+ )
221
+ updated_at: datetime.datetime = Field(
222
+ default_factory=datetime.datetime.utcnow,
223
+ description="Timestamp when the record was last updated"
224
+ )
225
+ custom_format: Optional[dict] = Field(description="Extra configuration for different type of slides")
226
+
227
+
228
+ class Presentation(BasePydanticModel, BaseObjectOrm):
229
+ id: int
230
+ folder: int
231
+ title: str = Field(..., max_length=255)
232
+ description: Optional[str] = Field("", description="Optional text")
233
+ created_at: datetime.datetime
234
+ updated_at: datetime.datetime
235
+ theme: Union[int,Theme]
236
+ slides:List[Slide]
237
+
238
+
239
+
240
+ @classmethod
241
+ def get_or_create_by_title(cls, *args, **kwargs):
242
+ url = f"{cls.get_object_url()}/get_or_create_by_title/"
243
+ payload = {"json": kwargs}
244
+ r = make_request(
245
+ s=cls.build_session(),
246
+ loaders=cls.LOADERS,
247
+ r_type="POST",
248
+ url=url,
249
+ payload=payload
250
+ )
251
+ if r.status_code not in [200, 201]:
252
+ raise Exception(f"Error appending creating: {r.text}")
253
+ # Return a new instance of AssetCategory built from the response JSON.
254
+ return cls(**r.json())
255
+
256
+
257
+ def add_slide(self, *args, **kwargs)->Slide:
258
+ url = f"{self.get_object_url()}/{self.id}/add_slide/"
259
+
260
+
261
+ r = make_request(
262
+ s=self.build_session(),
263
+ loaders=self.LOADERS,
264
+ r_type="POST",
265
+ url=url,payload={"json":{}}
266
+ )
267
+ if r.status_code not in [ 201]:
268
+ raise Exception(f"Error appending creating: {r.text}")
269
+ # Return a new instance of AssetCategory built from the response JSON.
270
+ return Slide(**r.json())
271
+
272
+
273
+ ##########
274
+
275
+ class TextNode(BaseModel):
276
+ type: Literal["text"] = Field(..., description="Inline text node")
277
+ text: str = Field(..., description="Text content")
278
+
279
+
280
+ class TextParagraphAttrs(BaseModel):
281
+ textAlign: Optional[Literal["left", "right", "center", "justify"]] = Field(
282
+ None, description="Text alignment within the block"
283
+ )
284
+ level: Optional[int] = Field(
285
+ None, ge=1, le=6,
286
+ description="Heading level (1–6); only set when `type` == 'heading'"
287
+ )
288
+
289
+
290
+ class TextParagraph(BaseModel):
291
+ """
292
+ Tiptap 'paragraph' or 'heading' block with inline text content.
293
+ """
294
+ type: Literal["paragraph", "heading"] = Field(
295
+ ..., description="Block type: 'paragraph' or 'heading'"
296
+ )
297
+ attrs: TextParagraphAttrs = Field(
298
+ default_factory=TextParagraphAttrs,
299
+ description="Block attributes (alignment and optional heading level)"
300
+ )
301
+ content: List[TextNode] = Field(
302
+ ..., description="Inline text nodes"
303
+ )
304
+
305
+ @classmethod
306
+ def paragraph(
307
+ cls,
308
+ text: str,
309
+ text_align: Optional[Literal["left", "right", "center", "justify"]] = None
310
+ ) -> "TextParagraph":
311
+ """
312
+ Build a simple paragraph block:
313
+
314
+ TextParagraph.paragraph("Hello world", text_align="center")
315
+ """
316
+ return cls(
317
+ type="paragraph",
318
+ attrs=TextParagraphAttrs(textAlign=text_align),
319
+ content=[TextNode(type="text", text=text)]
320
+ )
321
+
322
+ @classmethod
323
+ def heading(
324
+ cls,
325
+ text: str,
326
+ level: int = 1,
327
+ text_align: Optional[Literal["left", "right", "center", "justify"]] = None
328
+ ) -> "TextParagraph":
329
+ """
330
+ Build a heading block of the given level (1–6):
331
+
332
+ TextParagraph.heading("Title", level=2, text_align="center")
333
+ """
334
+ return cls(
335
+ type="heading",
336
+ attrs=TextParagraphAttrs(textAlign=text_align, level=level),
337
+ content=[TextNode(type="text", text=text)]
338
+ )
339
+
340
+ class Config:
341
+ json_schema_extra = {
342
+ "examples": {
343
+ "paragraph": {
344
+ "summary": "Basic paragraph",
345
+ "value": {
346
+ "type": "paragraph",
347
+ "attrs": {"textAlign": None},
348
+ "content": [{"type": "text", "text": "This is a body paragraph."}]
349
+ }
350
+ },
351
+ "heading": {
352
+ "summary": "Centered H1 heading",
353
+ "value": {
354
+ "type": "heading",
355
+ "attrs": {"textAlign": "center", "level": 1},
356
+ "content": [{"type": "text", "text": "This is a heading"}]
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ ### App Nodes
363
+ class EndpointProps(BaseModel):
364
+ props: dict[str, int] = Field(
365
+ ...,
366
+ description="Dictionary of props to send to the endpoint, e.g. {'id': 33}",
367
+ example={"id": 33}
368
+ )
369
+ url: str = Field(
370
+ ...,
371
+ description="Relative or absolute URL for the API endpoint",
372
+ example="/orm/api/reports/run-function/"
373
+ )
374
+
375
+ class AppNodeAttrs(BaseModel):
376
+ endpointProps: EndpointProps = Field(
377
+ ...,
378
+ description="Configuration for the endpoint call"
379
+ )
380
+
381
+ class AppNode(BaseModel):
382
+ """
383
+ Represents a custom Tiptap node of type 'appNode' that invokes an API.
384
+ """
385
+ type: Literal["appNode"] = Field(
386
+ "appNode",
387
+ description="Node type identifier"
388
+ )
389
+ attrs: AppNodeAttrs = Field(
390
+ ...,
391
+ description="Node attributes"
392
+ )
393
+
394
+ class Config:
395
+ json_schema_extra = {
396
+ "example": {
397
+ "type": "appNode",
398
+ "attrs": {
399
+ "endpointProps": {
400
+ "props": {"id": 33},
401
+ "url": "/orm/api/reports/run-function/"
402
+ }
403
+ }
404
+ }
405
+ }
406
+ @classmethod
407
+ def make_app_node(cls,id: int, url: str = "/orm/api/reports/run-function/") -> "AppNode":
408
+ return cls(
409
+ attrs=AppNodeAttrs(
410
+ endpointProps=EndpointProps(props={"id": id}, url=url)
411
+ )
412
+ )