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,292 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import base64
4
+ import os
5
+ from datetime import datetime
6
+ from io import BytesIO
7
+ from typing import List
8
+
9
+ from jinja2 import Environment, FileSystemLoader
10
+ import numpy as np
11
+ import pandas as pd
12
+ import plotly.express as px
13
+ import plotly.graph_objs as go
14
+
15
+ from mainsequence.tdag import APIDataNode
16
+ from mainsequence.virtualfundbuilder.utils import get_vfb_logger
17
+
18
+ from pydantic import BaseModel
19
+ from jinja2 import Template
20
+
21
+ from mainsequence.client import DoesNotExist, AssetCategory
22
+ from mainsequence.client.models_tdag import Artifact
23
+ from mainsequence.virtualfundbuilder.resource_factory.app_factory import register_app, BaseApp
24
+
25
+ logger = get_vfb_logger()
26
+
27
+ def example_data(assets):
28
+ """
29
+ Fetch real data from the 'api_ts.get_df_between_dates()' call, then:
30
+ 1) Build a time-series chart of 'Revenue' vs. time for each asset (ticker).
31
+ 2) Build a correlation heatmap of 'Revenue' vs. 'EPS' for the latest time period.
32
+ 3) Return both figures as Base64-encoded PNGs.
33
+ """
34
+
35
+ # ------------------------------------------------------------------------------
36
+ # 1) GET THE REAL DATA
37
+ market_time_serie_unique_identifier = "polygon_historical_fundamentals"
38
+ try:
39
+ from mainsequence.client import MarketsTimeSeriesDetails
40
+ hbs = MarketsTimeSeriesDetails.get(unique_identifier=market_time_serie_unique_identifier)
41
+ except DoesNotExist as e:
42
+ logger.exception(f"HistoricalBarsSource does not exist for {market_time_serie_unique_identifier}")
43
+ raise e
44
+
45
+ api_ts = APIDataNode(
46
+ data_source_id=hbs.related_local_time_serie.data_source_id,
47
+ update_hash=hbs.related_local_time_serie.update_hash
48
+ )
49
+
50
+ # This returns a DataFrame, indexed by (time_index, unique_identifier)
51
+ # with columns like: 'is_revenues', 'is_basic_earnings_per_share', etc.
52
+ data = api_ts.get_df_between_dates()
53
+
54
+ # Move the multi-index to columns for easier manipulation
55
+ df = data.reset_index()
56
+ # Example: 'unique_identifier' might be "AAPL_ms_share_class_123xyz"
57
+ # We'll extract the first underscore-delimited part as the 'asset_id'.
58
+ df["asset_id"] = df["unique_identifier"].str.split("_").str[0]
59
+
60
+ df = df[df["unique_identifier"].isin([a.unique_identifier for a in assets])]
61
+
62
+ # Rename columns to something more readable in the charts
63
+ df["Revenue"] = df["is_revenues"]
64
+ df["EPS"] = df["is_basic_earnings_per_share"]
65
+
66
+ # OPTIONAL: If you want to drop rows that have no revenue or EPS data
67
+ # df.dropna(subset=["Revenue", "EPS"], how="all", inplace=True)
68
+
69
+ # ------------------------------------------------------------------------------
70
+ # 2) TIME-SERIES LINE CHART: Revenue over time, color by ticker
71
+ import plotly.express as px
72
+ fig_line = px.line(
73
+ df,
74
+ x="time_index",
75
+ y="Revenue",
76
+ color="asset_id",
77
+ title="Revenue Over Time by Asset"
78
+ )
79
+ fig_line.update_layout(xaxis_title="Date", yaxis_title="Revenue")
80
+
81
+ # ------------------------------------------------------------------------------
82
+ # 3) CORRELATION HEATMAP
83
+ latest_date = df.groupby("unique_identifier")["quarter"].max().min() # latest date where all values are present
84
+ df_latest = df[df["quarter"] == latest_date].copy()
85
+
86
+ # Pivot so each row is an asset and columns are the fundamental metrics
87
+ # (Here, "Revenue" and "EPS").
88
+ df_pivot = df_latest.pivot_table(
89
+ index="asset_id",
90
+ values=["Revenue", "EPS"],
91
+ aggfunc="mean" # or 'first' if each (asset, time_index) is unique
92
+ )
93
+
94
+ corr_matrix = df_pivot.corr()
95
+ import plotly.graph_objs as go
96
+ fig_heatmap = go.Figure(
97
+ data=go.Heatmap(
98
+ z=corr_matrix.values,
99
+ x=corr_matrix.columns,
100
+ y=corr_matrix.index,
101
+ colorscale="Blues"
102
+ )
103
+ )
104
+ fig_heatmap.update_layout(
105
+ title=f"Correlation of Fundamentals on {latest_date}"
106
+ )
107
+
108
+ # ------------------------------------------------------------------------------
109
+ # 4) CONVERT PLOTS TO BASE64 STRINGS
110
+ import base64
111
+ from io import BytesIO
112
+
113
+ def fig_to_base64(fig):
114
+ """Render a Plotly figure to a PNG and return a base64 string."""
115
+ buf = BytesIO()
116
+ fig.write_image(buf, format="png")
117
+ buf.seek(0)
118
+ return base64.b64encode(buf.read()).decode("utf-8")
119
+
120
+ chart1_base64 = fig_to_base64(fig_line)
121
+ chart2_base64 = fig_to_base64(fig_heatmap)
122
+
123
+ return chart1_base64, chart2_base64
124
+
125
+ class ExampleReportConfig(BaseModel):
126
+ """Pydantic model defining the parameters for report generation."""
127
+ report_id: str = "MC-2025"
128
+ report_title: str = "Global Strategy Views: Diversify to Amplify"
129
+ bucket_name: str = "Reports"
130
+ authors: str = "Main Sequence AI"
131
+ sector: str = "US Equities"
132
+ region: str = "USA"
133
+ topics: List[str] = ["Diversification", "Equities", "Fundamentals"]
134
+ asset_category_unique_identifier: str = "magnificent_7"
135
+ summary: str = (
136
+ "We are entering a more benign phase of the economic cycle characterized by "
137
+ "sustained economic growth and declining policy interest rates. Historically, "
138
+ "such an environment supports equities but also highlights the increasing "
139
+ "importance of broad diversification across regions and sectors."
140
+ )
141
+
142
+ @register_app()
143
+ class ExampleReportApp(BaseApp):
144
+ """
145
+ Minimal example of a 'ReportApp' that can:
146
+ 1) Generate dummy data and create charts (line + heatmap).
147
+ 2) Embed those charts into an HTML template.
148
+ 3) Optionally export the HTML to PDF using WeasyPrint.
149
+ """
150
+ configuration_class = ExampleReportConfig
151
+ def __init__(self, *args, **kwargs):
152
+ super().__init__(*args, **kwargs)
153
+
154
+ category = AssetCategory.get(unique_identifier=self.configuration.asset_category_unique_identifier)
155
+ self.assets = category.get_assets()
156
+ self.category_name = category.display_name
157
+
158
+ def _fig_to_base64(self, fig) -> str:
159
+ """
160
+ Render a Plotly figure to PNG and return a Base64 string.
161
+ """
162
+ buf = BytesIO()
163
+ fig.write_image(buf, format="png")
164
+ buf.seek(0)
165
+ return base64.b64encode(buf.read()).decode("utf-8")
166
+
167
+ def run(self):
168
+ """
169
+ Generates an HTML report (and optional PDF) in a minimal, self-contained way.
170
+ """
171
+ print(f"Running tool with configuration {self.configuration}")
172
+
173
+ # 1) Retrieve the chart images:
174
+ chart1_base64, chart2_base64 = example_data(self.assets)
175
+
176
+ # Build context from config
177
+ template_context = {
178
+ "current_date": datetime.now().strftime('%Y-%m-%d'),
179
+ "current_year": datetime.now().year,
180
+ "logo_location": f"https://main-sequence.app/static/media/logos/MS_logo_long_white.png",
181
+ # Pulling fields from our pydantic config:
182
+ "report_id": self.configuration.report_id,
183
+ "authors": self.configuration.authors,
184
+ "sector": self.configuration.sector,
185
+ "region": self.configuration.region,
186
+ "topics": self.configuration.topics,
187
+ "report_title": self.configuration.report_title,
188
+ "summary": self.configuration.summary,
189
+ "report_content": f"""
190
+ <h2>Overview</h2>
191
+ <p>
192
+ Longer-term interest rates are expected to remain elevated, driven by rising government deficits
193
+ and persistent term premiums. However, the reduced likelihood of a near-term recession presents
194
+ opportunities for positive equity returns, notably in sectors like technology and select value-oriented
195
+ areas such as financials.
196
+ </p>
197
+ <p>
198
+ This evolving landscape emphasizes the necessity of expanding our investment horizon beyond traditional
199
+ focuses—such as large-cap US technology—to include regional markets, mid-cap companies, and "Ex-Tech Compounders."
200
+ Such diversification aims to enhance risk-adjusted returns as global growth trajectories become more aligned.
201
+ </p>
202
+ <h2>Key Takeaways</h2>
203
+ <ul>
204
+ <li>
205
+ <strong>Diversification enhances return potential:</strong> Capturing alpha in the upcoming cycle
206
+ will likely depend on a diversified approach across multiple regions and investment factors.
207
+ </li>
208
+ <li>
209
+ <strong>Technology remains essential:</strong> Rising demand for physical infrastructure, such as
210
+ data centers and AI-supportive hardware, will benefit traditional industrial sectors, creating
211
+ new investment opportunities.
212
+ </li>
213
+ <li>
214
+ <strong>Divergent interest rate dynamics:</strong> Central banks have started easing policies, but
215
+ persistent high bond yields imply limitations on further equity valuation expansions.
216
+ </li>
217
+ </ul>
218
+
219
+ <!-- Page break before next section if printing to PDF -->
220
+ <div style="page-break-after: always;"></div>
221
+
222
+ <h2>Fundamental Trends and Correlation Analysis</h2>
223
+ <p>
224
+ The following charts illustrate recent fundamental trends among selected US equities, focusing specifically
225
+ on revenue performance over recent reporting periods. This analysis leverages data obtained via the internal
226
+ "pylong" API, clearly highlighting the evolving top-line dynamics across multiple companies.
227
+ </p>
228
+ <p style="text-align:center;">
229
+ <img alt="Revenue Over Time"
230
+ src="data:image/png;base64,{chart1_base64}"
231
+ style="max-width:600px; width:100%;">
232
+ </p>
233
+
234
+ <p>
235
+ Further, the correlation heatmap below illustrates the relationships between key fundamental indicators—such
236
+ as Revenue and Earnings Per Share (EPS)—across companies at the latest reporting date. This visualization
237
+ provides strategic insights into how closely fundamental metrics move together, enabling more informed
238
+ portfolio allocation decisions.
239
+ </p>
240
+ <p style="text-align:center;">
241
+ <img alt="Correlation Heatmap"
242
+ src="data:image/png;base64,{chart2_base64}"
243
+ style="max-width:600px; width:100%;">
244
+ </p>
245
+
246
+ <p>
247
+ As the macroeconomic environment evolves, shifts in these fundamental correlations may offer additional
248
+ opportunities for strategic repositioning and optimized sector exposures.
249
+ </p>
250
+ """
251
+ }
252
+
253
+ """
254
+ Renders a static HTML report from Jinja2 templates, embedding two charts as Base64 images,
255
+ and (optionally) saves it as PDF using WeasyPrint.
256
+ """
257
+ # 2) Setup the Jinja2 environment: (point to the templates directory)
258
+ template_dir = os.path.join(os.path.dirname(__file__), 'templates')
259
+ env = Environment(loader=FileSystemLoader(template_dir), autoescape=False)
260
+
261
+ # 3) Load the derived template (which should reference placeholders in Jinja syntax).
262
+ template = env.get_template('report.html')
263
+
264
+ # 5) Render the HTML:
265
+ rendered_html = template.render(template_context)
266
+
267
+ # 6) Write the rendered HTML to a file
268
+ output_html_path = os.path.join(os.path.dirname(__file__), 'output_report.html')
269
+ with open(output_html_path, 'w', encoding='utf-8') as f:
270
+ f.write(rendered_html)
271
+
272
+ print(f"HTML report generated: {output_html_path}")
273
+
274
+ print("Generated HTML:", output_html_path)
275
+
276
+ # from weasyprint import HTML
277
+ # pdf_path = "/tmp/report.pdf"
278
+ # HTML(string=rendered_html).write_pdf(pdf_path)
279
+ # print(f"PDF generated: {pdf_path}")
280
+ # pdf_artifact = Artifact.upload_file(filepath=pdf_path, name="Report PDF", created_by_resource_name=self.__class__.__name__, bucket_name="Reports")
281
+ html_artifact = Artifact.upload_file(
282
+ filepath=output_html_path,
283
+ name=self.configuration.report_id,
284
+ created_by_resource_name=self.__class__.__name__,
285
+ bucket_name=self.configuration.bucket_name
286
+ )
287
+
288
+ if __name__ == "__main__":
289
+ # Example usage:
290
+ config = ExampleReportConfig() # Or override fields as needed
291
+ app = ExampleReportApp(config)
292
+ html_artifact = app.run() # Creates output_report.html and weasy_output_report.pdf
@@ -0,0 +1,107 @@
1
+ import pandas as pd
2
+
3
+ from mainsequence.client import Asset, AssetCategory, CONSTANTS
4
+ from mainsequence.virtualfundbuilder.contrib.prices.data_nodes import ExternalPrices
5
+ from mainsequence.virtualfundbuilder.portfolio_interface import PortfolioInterface
6
+ from mainsequence.virtualfundbuilder.utils import get_vfb_logger
7
+
8
+ from pydantic import BaseModel
9
+ from mainsequence.client.models_tdag import Artifact
10
+ from mainsequence.virtualfundbuilder.resource_factory.app_factory import BaseApp, register_app
11
+
12
+ logger = get_vfb_logger()
13
+
14
+ class LoadExternalPortfolioConfig(BaseModel):
15
+ bucket_name: str
16
+ artifact_name: str
17
+ portfolio_name: str
18
+ created_asset_category_name: str
19
+
20
+ @register_app()
21
+ class LoadExternalPortfolio(BaseApp):
22
+ configuration_class = LoadExternalPortfolioConfig
23
+
24
+ def run(self):
25
+
26
+ # get the data and store it on local storage
27
+ source_artifact = Artifact.get(bucket__name=self.configuration.bucket_name, name=self.configuration.artifact_name)
28
+ weights_source = pd.read_csv(source_artifact.content)
29
+
30
+ # validate data
31
+ expected_cols = [
32
+ "time_index",
33
+ "figi",
34
+ "weight",
35
+ "price"
36
+ ]
37
+ if set(weights_source.columns) != set(expected_cols):
38
+ raise ValueError(
39
+ f"Invalid CSV format: expected columns {expected_cols!r} "
40
+ f"but got {list(weights_source.columns)!r}"
41
+ )
42
+
43
+ weights_source["time_index"] = pd.to_datetime(
44
+ weights_source["time_index"], utc=True
45
+ )
46
+
47
+ # create assets from figi in the backend
48
+ assets = []
49
+ for figi in weights_source["figi"].unique():
50
+ asset = Asset.get_or_none(figi=figi)
51
+ if asset is None:
52
+ try:
53
+ asset = Asset.register_figi_as_asset_in_main_sequence_venue(
54
+ figi=figi,
55
+ execution_venue__symbol=CONSTANTS.MAIN_SEQUENCE_EV,
56
+ )
57
+ except Exception as e:
58
+ print(f"Could not register asset with figi {figi}, error {e}")
59
+ continue
60
+ assets.append(asset)
61
+
62
+ # create asset category
63
+ portfolio_category = AssetCategory.get_or_create(
64
+ display_name=self.configuration.created_asset_category_name,
65
+ source="external",
66
+ description=f"This category contains the assets for the external portfolio {self.configuration.portfolio_name}",
67
+ unique_identifier=self.configuration.created_asset_category_name.replace(" ", "_").lower(),
68
+ )
69
+ portfolio_category.append_assets([a.id for a in assets])
70
+
71
+ # insert prices
72
+ external_prices_source = ExternalPrices(
73
+ bucket_name=self.configuration.bucket_name,
74
+ artifact_name=self.configuration.artifact_name,
75
+ asset_category_unique_id=portfolio_category.unique_identifier
76
+ ).run(debug_mode=True, force_update=True)
77
+
78
+ # adapt portfolio configuration
79
+ portfolio = PortfolioInterface.load_from_configuration("external_portfolio_template")
80
+ current_template_dict = portfolio.portfolio_config_template
81
+
82
+ sw_config = current_template_dict['portfolio_build_configuration']['backtesting_weights_configuration']['signal_weights_configuration']
83
+ sw_config['bucket_name'] = self.configuration.bucket_name
84
+ sw_config['artifact_name'] = self.configuration.artifact_name
85
+ sw_config['assets_category_unique_id'] = portfolio_category.unique_identifier
86
+ sw_config['signal_assets_configuration']['assets_category_unique_id'] = portfolio_category.unique_identifier
87
+ current_template_dict['portfolio_markets_configuration']['portfolio_name'] = self.configuration.portfolio_name
88
+
89
+ portfolio = PortfolioInterface(current_template_dict)
90
+
91
+ # Switch out the prices source to get our external prices
92
+ portfolio._initialize_nodes()
93
+ portfolio.portfolio_strategy_data_node.bars_ts = external_prices_source
94
+
95
+ # Run the portfolio
96
+ res = portfolio.run(add_portfolio_to_markets_backend=True)
97
+ logger.info(f"Portfolio integrated successfully with results {res.head()}")
98
+
99
+ if __name__ == "__main__":
100
+ app_config = LoadExternalPortfolioConfig(
101
+ bucket_name="Sample Data",
102
+ artifact_name="portfolio_weights_mag7.csv",
103
+ portfolio_name="Mag 7 External Portfolio",
104
+ created_asset_category_name="external_magnificent_7",
105
+ )
106
+
107
+ LoadExternalPortfolio(app_config).run()