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,284 @@
1
+ import logging
2
+ import logging.config
3
+ import os
4
+ from pathlib import Path
5
+
6
+ import requests
7
+ import structlog
8
+ from typing import Union
9
+
10
+ from requests.structures import CaseInsensitiveDict
11
+ from structlog.dev import ConsoleRenderer
12
+ from .instrumentation import OTelJSONRenderer
13
+ logger = None
14
+ from structlog.stdlib import BoundLogger
15
+ from typing import Dict, Any
16
+ import inspect
17
+ import importlib
18
+ from structlog.threadlocal import wrap_dict
19
+ import sys
20
+
21
+ def ensure_dir(file_path):
22
+ directory = os.path.dirname(file_path)
23
+ Path(directory).mkdir(parents=True, exist_ok=True)
24
+
25
+ def add_structlog_event_to_record(logger, method_name, event_dict):
26
+ record = event_dict.get('_record')
27
+ if record is not None:
28
+ # Remove '_record' to prevent circular reference
29
+ event_dict.pop('_record', None)
30
+ record.structlog_event = event_dict.copy()
31
+ return event_dict
32
+
33
+ class CustomConsoleRenderer(ConsoleRenderer):
34
+ def __call__(self, logger, name, event_dict):
35
+ # Extract call site parameters
36
+ lineno = event_dict.pop('lineno', None)
37
+ filename = event_dict.pop('filename', None)
38
+ func_name = event_dict.pop('func_name', None)
39
+ # application_name = event_dict.pop('application_name', None)
40
+ # update_hash=event_dict.pop('update_hash', "")
41
+ # Call the parent renderer
42
+ rendered = super().__call__(logger, name, event_dict)
43
+ # Append the call site information to the rendered output
44
+ if filename and lineno and func_name:
45
+ rendered += f" (at {filename}:{lineno} in {func_name}())"
46
+ elif filename and lineno:
47
+ rendered += f" (at {filename}:{lineno})"
48
+ return rendered
49
+
50
+ def build_application_logger(
51
+ application_name:str="ms-sdk",
52
+ **metadata
53
+ ):
54
+ """
55
+ Create a logger that logs to console and file in JSON format.
56
+ """
57
+
58
+ # do initial request when on logger initialization
59
+ headers = CaseInsensitiveDict()
60
+ headers["Content-Type"] = "application/json"
61
+ headers["Authorization"] = "Token " + os.getenv("MAINSEQUENCE_TOKEN")
62
+
63
+ project_info_endpoint = f'{os.getenv("TDAG_ENDPOINT")}/orm/api/pods/job/get_job_startup_state/'
64
+
65
+ command_id = os.getenv("COMMAND_ID")
66
+ params = {}
67
+ if command_id:
68
+ params['command_id'] = command_id
69
+
70
+ response = requests.get(project_info_endpoint, headers=headers, params=params)
71
+
72
+ if response.status_code != 200:
73
+ print(f"Got Status Code {response.status_code} with response {response.text}")
74
+
75
+ json_response = response.json()
76
+ if "project_id" not in json_response:
77
+ raise ValueError(f"Project ID not found, server response {json_response}")
78
+
79
+ # set additional args from backend
80
+ if "additional_environment" in json_response:
81
+ for key, value in json_response["additional_environment"].items():
82
+ os.environ[key] = value
83
+
84
+ # Get logger path in home directory if no path is set in environemnt
85
+ tdag_base_path = Path(os.getenv("TDAG_ROOT_PATH", Path.home() / ".tdag"))
86
+ default_log_path = tdag_base_path / "logs" / "tdag.log"
87
+ logger_file = os.getenv("LOGGER_FILE_PATH", str(default_log_path))
88
+
89
+ logger_name = "tdag"
90
+
91
+ # Define the timestamper and pre_chain processors
92
+ timestamper = structlog.processors.TimeStamper( fmt="iso",
93
+ utc=True,)
94
+ pre_chain = [
95
+ structlog.stdlib.add_log_level,
96
+ structlog.stdlib.ExtraAdder(),
97
+ timestamper,
98
+ ]
99
+
100
+ handlers = {
101
+ "console": {
102
+ "class": "logging.StreamHandler",
103
+ "formatter": "colored",
104
+ "level":os.getenv("LOG_LEVEL", "DEBUG")
105
+ },
106
+ }
107
+ if logger_file is not None:
108
+ ensure_dir(logger_file) # Ensure the directory for the log file exists
109
+
110
+ handlers.update(
111
+ {
112
+ "file": {
113
+ "class": "concurrent_log_handler.ConcurrentRotatingFileHandler",
114
+ "formatter": "plain",
115
+ "level": os.getenv("LOG_LEVEL_FILE", "DEBUG"),
116
+ "filename": logger_file,
117
+ "mode": "a",
118
+ "delay": True,
119
+ "maxBytes": 5 * 1024 * 1024, # Rotate after 5 MB
120
+ "backupCount": 5, # Keep up to 5 backup files
121
+ }
122
+ }
123
+ )
124
+
125
+ logging_config = {
126
+ "version": 1,
127
+ "disable_existing_loggers": False,
128
+ "formatters": {
129
+ "plain": {
130
+ "()": structlog.stdlib.ProcessorFormatter,
131
+ "processor":OTelJSONRenderer(),#structlog.processors.JSONRenderer(),
132
+ "foreign_pre_chain": pre_chain,
133
+ },
134
+ "colored": {
135
+ "()": structlog.stdlib.ProcessorFormatter,
136
+ "processor": CustomConsoleRenderer(colors=True),
137
+ "foreign_pre_chain": pre_chain,
138
+ },
139
+ },
140
+ "handlers":handlers,
141
+ "loggers": {
142
+ logger_name: {
143
+ "handlers":list(handlers.keys()),
144
+ "level": os.getenv("LOG_LEVEL_STDOUT", "INFO"),
145
+ "propagate": False,
146
+ },
147
+ },
148
+ }
149
+ try:
150
+ logging.config.dictConfig(logging_config)
151
+ except Exception as e:
152
+ raise e
153
+ # Configure structlog
154
+ structlog.configure(
155
+ processors=[
156
+ structlog.contextvars.merge_contextvars, # context that always appears in the logs
157
+ structlog.stdlib.add_log_level,
158
+ structlog.stdlib.PositionalArgumentsFormatter(),
159
+ structlog.processors.CallsiteParameterAdder(
160
+ {
161
+ structlog.processors.CallsiteParameter.FILENAME,
162
+ structlog.processors.CallsiteParameter.FUNC_NAME,
163
+ structlog.processors.CallsiteParameter.LINENO,
164
+ }
165
+ ),
166
+ timestamper,
167
+ structlog.processors.StackInfoRenderer(),
168
+ structlog.processors.format_exc_info, # suggested to remove for pretty exceptions
169
+ add_structlog_event_to_record, # Add this processor before wrap_for_formatter
170
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
171
+
172
+ ],
173
+ context_class=dict,
174
+ logger_factory=structlog.stdlib.LoggerFactory(),
175
+ wrapper_class=structlog.make_filtering_bound_logger(logging.DEBUG),
176
+ cache_logger_on_first_use=True,
177
+ )
178
+
179
+ # Create the structlog logger and bind metadata
180
+ logger = structlog.get_logger(logger_name)
181
+ logger = logger.bind(application_name=application_name,**metadata)
182
+
183
+ try:
184
+ logger = logger.bind(project_id=json_response["project_id"], **metadata)
185
+ logger = logger.bind(data_source_id=json_response["data_source_id"], **metadata)
186
+ logger = logger.bind(job_run_id=json_response["job_run_id"], **metadata)
187
+ logger = logger.bind(command_id=int(command_id) if command_id else None, **metadata)
188
+
189
+ except Exception as e:
190
+ logger.exception(f"Could not retrive pod project {e}")
191
+ raise e
192
+
193
+ logger = logger.bind()
194
+ return logger
195
+
196
+
197
+ def dump_structlog_bound_logger(logger: BoundLogger) -> Dict[str, Any]:
198
+ """
199
+ Serialize a fully‑initialized structlog BoundLogger into a dict:
200
+ - Global structlog config (as import paths)
201
+ - Underlying stdlib.Logger name & level
202
+ - Bound key/value context
203
+
204
+ Returns:
205
+ A dict that can be json-serialized and later reloaded to reconstruct
206
+ the same structlog setup in another process.
207
+ """
208
+
209
+ # Helper: get module.QualName for function/class or for an instance's class
210
+ def pathify(obj: Any) -> str:
211
+ target = obj if inspect.isfunction(obj) or inspect.isclass(obj) else obj.__class__
212
+ return f"{target.__module__}.{target.__qualname__}"
213
+
214
+ # 1) Global structlog config
215
+ cfg = structlog.get_config()
216
+ structlog_config = {
217
+ "processors": [pathify(p) for p in cfg["processors"]],
218
+ "logger_factory": pathify(cfg["logger_factory"]),
219
+ "wrapper_class": pathify(cfg["wrapper_class"]),
220
+ "context_class": pathify(cfg["context_class"]),
221
+ "cache_logger_on_first_use": cfg["cache_logger_on_first_use"],
222
+ }
223
+
224
+ # 2) Underlying stdlib.Logger info
225
+ std = logger._logger
226
+ logger_name = std.name
227
+ logger_level = std.level
228
+
229
+ # 3) Bound context
230
+ bound_context = dict(logger._context or {})
231
+
232
+ # 4) Assemble and return
233
+ return {
234
+ "structlog_config": structlog_config,
235
+ "logger_name": logger_name,
236
+ "logger_level": logger_level,
237
+ "bound_context": bound_context,
238
+ }
239
+
240
+
241
+ def load_structlog_bound_logger(dump: Dict[str, Any]) -> BoundLogger:
242
+ """
243
+ Given the dict from dump_structlog_bound_logger(),
244
+ return a BoundLogger with the same name, level, and context,
245
+ but using the EXISTING global structlog configuration.
246
+ """
247
+ name = dump["logger_name"]
248
+ level = dump["logger_level"]
249
+ bound_context = dump["bound_context"]
250
+
251
+ # 1) Grab the already‐configured logger
252
+ base: BoundLogger = structlog.get_logger(name)
253
+
254
+ # 2) (Optional) restore its stdlib level
255
+ std = getattr(base, "_logger", None)
256
+ if std is not None:
257
+ std.setLevel(level)
258
+
259
+ # 3) Re‐bind the original context
260
+ return base.bind(**bound_context)
261
+
262
+ logger = build_application_logger()
263
+
264
+ # create a new system exection hook to also log terminating exceptions
265
+ original_hook = sys.excepthook
266
+
267
+ def handle_exception(exc_type, exc_value, exc_traceback):
268
+ """
269
+ A custom exception handler that logs any uncaught exception.
270
+ """
271
+ if issubclass(exc_type, KeyboardInterrupt):
272
+ # Let the user interrupt the program without a traceback
273
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
274
+ return
275
+
276
+ # Log the exception using our configured logger
277
+ logger.error(
278
+ "Uncaught exception",
279
+ exc_info=(exc_type, exc_value, exc_traceback)
280
+ )
281
+ # Also call the original hook to print the traceback to the console
282
+ original_hook(exc_type, exc_value, exc_traceback)
283
+
284
+ sys.excepthook = handle_exception
File without changes
File without changes