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,220 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from typing import Any, Callable, Mapping, MutableMapping, Optional, Tuple, Union
5
+
6
+ import streamlit as st
7
+
8
+ from mainsequence.dashboards.streamlit.core.theme import inject_css_for_dark_accents, override_spinners
9
+ from importlib.resources import files as _pkg_files
10
+ import sys
11
+ import os
12
+
13
+ def _detect_app_dir() -> Path:
14
+ """
15
+ Best-effort detection of the directory that contains the running Streamlit app.
16
+ Priority:
17
+ 1) sys.modules['__main__'].__file__ (how Streamlit executes scripts)
18
+ 2) Streamlit script run context (main_script_path) if available
19
+ 3) env override MS_APP_DIR / STREAMLIT_APP_DIR
20
+ 4) fallback: Path.cwd()
21
+ """
22
+ # 1) __main__.__file__
23
+ try:
24
+ main_mod = sys.modules.get("__main__")
25
+ if main_mod and getattr(main_mod, "__file__", None):
26
+ return Path(main_mod.__file__).resolve().parent
27
+ except Exception:
28
+ pass
29
+
30
+ # 2) Streamlit runtime (private API; guarded)
31
+ try:
32
+ from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
33
+ ctx = get_script_run_ctx()
34
+ if ctx and getattr(ctx, "main_script_path", None):
35
+ return Path(ctx.main_script_path).resolve().parent
36
+ except Exception:
37
+ pass
38
+
39
+ # 3) env override
40
+ for var in ("MS_APP_DIR", "STREAMLIT_APP_DIR"):
41
+ p = os.environ.get(var)
42
+ if p:
43
+ try:
44
+ return Path(p).resolve()
45
+ except Exception:
46
+ pass
47
+
48
+ # 4) fallback
49
+ return Path.cwd()
50
+
51
+
52
+ def _bootstrap_theme_from_package(
53
+ package: str = "mainsequence.dashboards.streamlit",
54
+ resource: str = "assets/config.toml", # keep this in assets/ (dot-dirs often excluded from wheels)
55
+ target_root: Path | None = None,
56
+ ) -> Path | None:
57
+ """
58
+ Ensure there's a streamlit/config.toml next to the app script.
59
+ If missing, copy the packaged default once and rerun so theme applies.
60
+ Returns the path to the config file (or None if not created).
61
+ """
62
+ # Read default theme from the package (if present)
63
+
64
+ try:
65
+ src = _pkg_files(package).joinpath(resource)
66
+
67
+ if not src.is_file():
68
+ return None # packaged file not present
69
+ default_toml = src.read_text(encoding="utf-8")
70
+ except Exception:
71
+
72
+ return None # no packaged theme; nothing to do
73
+
74
+ app_dir = target_root or _detect_app_dir()
75
+ cfg_dir = app_dir / ".streamlit"
76
+ cfg_file = cfg_dir / "config.toml"
77
+
78
+ if not cfg_file.exists():
79
+ try:
80
+ cfg_dir.mkdir(parents=True, exist_ok=True)
81
+ cfg_file.write_text(default_toml, encoding="utf-8")
82
+ # Avoid infinite loop: only rerun once
83
+ if not st.session_state.get("_ms_theme_bootstrapped"):
84
+ st.session_state["_ms_theme_bootstrapped"] = True
85
+ st.rerun()
86
+ except Exception:
87
+ # If we cannot write, just skip silently to avoid breaking the app
88
+ return None
89
+
90
+ return cfg_file
91
+
92
+ # --- App configuration contract (provided by the example app) -----------------
93
+
94
+ HeaderFn = Callable[[Any], None]
95
+ RouteFn = Callable[[Mapping[str, Any]], str]
96
+ ContextFn = Callable[[MutableMapping[str, Any]], Any]
97
+ InitSessionFn = Callable[[MutableMapping[str, Any]], None]
98
+ NotFoundFn = Callable[[], None]
99
+
100
+ @dataclass
101
+ class PageConfig:
102
+ title: str
103
+ build_context: Optional[ContextFn] =None # required
104
+
105
+ render_header: Optional[HeaderFn] = None # if None, minimal header
106
+ init_session: Optional[InitSessionFn] = None # set defaults in session_state
107
+
108
+ # Optional overrides; if None, scaffold uses its bundled defaults.
109
+ logo_path: Optional[Union[str, Path]] = None
110
+ page_icon_path: Optional[Union[str, Path]] = None
111
+
112
+ use_wide_layout: bool = True
113
+ hide_streamlit_multipage_nav: bool = False
114
+ inject_theme_css: bool = True
115
+
116
+ # --- Internal helpers ---------------------------------------------------------
117
+
118
+ _HIDE_NATIVE_NAV = """
119
+ <style>[data-testid='stSidebarNav']{display:none!important}</style>
120
+ """
121
+
122
+ def _hide_sidebar() -> None:
123
+ st.markdown("""
124
+ <style>
125
+ [data-testid="stSidebar"]{display:none!important;}
126
+ [data-testid="stSidebarCollapseControl"]{display:none!important;}
127
+ </style>
128
+ """, unsafe_allow_html=True)
129
+
130
+ def _minimal_header(title: str) -> None:
131
+ st.title(title)
132
+
133
+ def _resolve_assets(explicit_logo: Optional[Union[str, Path]],
134
+ explicit_icon: Optional[Union[str, Path]]) -> Tuple[Optional[str], Union[str, None], Optional[str]]:
135
+ """
136
+ Returns a tuple:
137
+ (logo_path_for_st_logo, page_icon_for_set_page_config, icon_path_for_st_logo_param)
138
+
139
+ - If no overrides are provided, uses scaffold defaults:
140
+ mainsequence.dashboards.streamlit/assets/logo.png
141
+ mainsequence.dashboards.streamlit/assets/favicon.png
142
+ - If favicon file is missing, falls back to emoji "📊" for set_page_config.
143
+ - st.logo() will only receive icon_image if a real file exists.
144
+ """
145
+ base_assets = Path(__file__).resolve().parent / "assets"
146
+ default_logo = base_assets / "logo.png"
147
+ default_favicon = base_assets / "favicon.png"
148
+
149
+ # Pick explicit override or default paths
150
+ logo_path = Path(explicit_logo) if explicit_logo else default_logo
151
+ icon_path = Path(explicit_icon) if explicit_icon else default_favicon
152
+
153
+ # Effective values
154
+ logo_for_logo_api: Optional[str] = str(logo_path) if logo_path.exists() else None
155
+ icon_for_page_config: Union[str, None]
156
+ icon_for_logo_param: Optional[str]
157
+
158
+ if icon_path.exists():
159
+ icon_for_page_config = str(icon_path)
160
+ icon_for_logo_param = str(icon_path)
161
+ else:
162
+ # Streamlit allows emoji for set_page_config, but st.logo needs a file path.
163
+ icon_for_page_config = "📊"
164
+ icon_for_logo_param = None
165
+
166
+ return logo_for_logo_api, icon_for_page_config, icon_for_logo_param
167
+
168
+ # --- Public entrypoint --------------------------------------------------------
169
+
170
+ # scaffold.py
171
+ def run_page(cfg: PageConfig):
172
+ """
173
+ Initialize page-wide look & feel, theme, context, and header.
174
+ Call this at the top of *every* Streamlit page (Home + pages/*).
175
+ Returns a context object (whatever build_context returns).
176
+ """
177
+ # 1) Page config should be the first Streamlit call
178
+ _logo, _page_icon, _icon_for_logo = _resolve_assets(cfg.logo_path, cfg.page_icon_path)
179
+ st.set_page_config(
180
+ page_title=cfg.title,
181
+ page_icon=_page_icon,
182
+ layout="wide" if cfg.use_wide_layout else "centered",
183
+ )
184
+
185
+ # 2) Optional: logo + CSS tweaks
186
+ if _logo:
187
+ st.logo(_logo, icon_image=_icon_for_logo)
188
+ if cfg.inject_theme_css:
189
+ inject_css_for_dark_accents()
190
+
191
+ # 3) Spinners (pure CSS)
192
+ override_spinners()
193
+
194
+ # 4) Do NOT hide the native nav unless explicitly asked
195
+ if cfg.hide_streamlit_multipage_nav:
196
+ st.markdown(_HIDE_NATIVE_NAV, unsafe_allow_html=True)
197
+
198
+ # 5) Session + context
199
+ if cfg.init_session:
200
+ cfg.init_session(st.session_state)
201
+
202
+ ctx={}
203
+ if cfg.build_context:
204
+ ctx = cfg.build_context(st.session_state)
205
+
206
+ # 6) Header
207
+ if cfg.render_header:
208
+ cfg.render_header(ctx)
209
+ else:
210
+ _minimal_header(cfg.title)
211
+
212
+ # 7) Create .streamlit/config.toml on first run (reruns once if created)
213
+ from pathlib import Path
214
+ print(Path.cwd())
215
+ _bootstrap_theme_from_package()
216
+
217
+ return ctx
218
+
219
+
220
+
@@ -0,0 +1,7 @@
1
+ from .utils import *
2
+
3
+
4
+
5
+ if 'tracer' not in locals():
6
+ tracer_instrumentator = TracerInstrumentator()
7
+ tracer = tracer_instrumentator.build_tracer()
@@ -0,0 +1,101 @@
1
+ import logging
2
+ import os
3
+ from opentelemetry import trace
4
+ from opentelemetry.trace import (
5
+ INVALID_SPAN,
6
+ INVALID_SPAN_CONTEXT,
7
+ get_current_span,
8
+ get_tracer_provider,
9
+ set_tracer_provider,
10
+ get_tracer,
11
+ Status,
12
+ SpanKind,
13
+ StatusCode,
14
+
15
+ )
16
+ from opentelemetry.sdk.trace import (
17
+ TracerProvider,
18
+
19
+ )
20
+ from opentelemetry.sdk.trace.export import (
21
+ BatchSpanProcessor,
22
+ ConsoleSpanExporter
23
+ )
24
+ from opentelemetry.trace.propagation.tracecontext import \
25
+ TraceContextTextMapPropagator
26
+ from typing import Union
27
+ import structlog
28
+
29
+ def is_port_in_use(port: int,agent_host:str) -> bool:
30
+ import socket
31
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
32
+ return s.connect_ex((agent_host, port)) == 0
33
+
34
+ class TracerInstrumentator():
35
+ __doc__ = f"""
36
+ Main instrumentator class controlls building and exporting of traces
37
+ """
38
+
39
+ def build_tracer(self) -> TraceContextTextMapPropagator:
40
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
41
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
42
+ resource = Resource(attributes={SERVICE_NAME: "tdag"})
43
+ set_tracer_provider(TracerProvider(resource=resource))
44
+
45
+ end_point = os.environ.get("OTLP_ENDPOINT")
46
+
47
+ if end_point is not None:
48
+ otlp_exporter = OTLPSpanExporter(endpoint=end_point)
49
+ if is_port_in_use(4317,agent_host=self.agent_host)== True:
50
+ get_tracer_provider().add_span_processor(BatchSpanProcessor(otlp_exporter))
51
+ else:
52
+ get_tracer_provider().add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
53
+ tracer = get_tracer("tdag")
54
+ return tracer
55
+
56
+ def get_current_trace_id(self):
57
+ current_span = get_current_span()
58
+ return format(current_span.context.trace_id, "032x")
59
+
60
+ def get_telemetry_carrier(self):
61
+ prop = TraceContextTextMapPropagator()
62
+ telemetry_carrier = {}
63
+ prop.inject(carrier=telemetry_carrier)
64
+ return telemetry_carrier
65
+
66
+ def append_attribute_to_current_span(self,attribute_key,attribute_value):
67
+ current_span = get_current_span()
68
+ current_span.set_attribute(attribute_key, attribute_value)
69
+
70
+ def add_otel_trace_context(logger, method_name, event_dict):
71
+ """
72
+ Enrich log records with OpenTelemetry trace context (trace_id, span_id).
73
+ """
74
+ span = trace.get_current_span()
75
+ if not span.is_recording():
76
+ event_dict["span"] = None
77
+ return event_dict
78
+
79
+ ctx = span.get_span_context()
80
+ parent = getattr(span, "parent", None)
81
+
82
+ event_dict["span"] = {
83
+ "span_id": format(ctx.span_id, "016x"),
84
+ "trace_id": format(ctx.trace_id, "032x"),
85
+ "parent_span_id": None if not parent else format(parent.span_id, "016x"),
86
+ }
87
+
88
+ return event_dict
89
+
90
+ class OTelJSONRenderer(structlog.processors.JSONRenderer):
91
+ """
92
+ A custom JSON renderer that injects OTel trace/span fields
93
+ immediately before serializing to JSON.
94
+ """
95
+ def __call__(self, logger, method_name, event_dict):
96
+ # 1) Grab the current active span from OpenTelemetry
97
+ event_dict=add_otel_trace_context(logger,method_name,event_dict)
98
+
99
+ # 3) Now call the base JSONRenderer to produce final JSON
100
+ return super().__call__(logger, method_name, event_dict)
101
+
@@ -0,0 +1 @@
1
+ from .instruments import *
@@ -0,0 +1,10 @@
1
+ from .data_interface import DateInfo, MockDataInterface, MSInterface
2
+ from mainsequence.instruments import settings
3
+
4
+ def _make_backend():
5
+ if getattr(settings, "data", None) and getattr(settings.data, "backend", "mock") == "mainsequence":
6
+ return MSInterface()
7
+ return MockDataInterface()
8
+
9
+ # export a single, uniform instance
10
+ data_interface = _make_backend()