1212 FeatureSnapshotRuntimeSettings ,
1313 evaluate_feature_snapshot_strategy ,
1414)
15+ from quant_platform_kit .common .capital_base import (
16+ CapitalBaseBinding ,
17+ CapitalScope ,
18+ CapitalValuationBasis ,
19+ build_capital_base_snapshot ,
20+ )
1521from quant_platform_kit .common .strategy_plugins import attach_strategy_plugin_metadata
1622from quant_platform_kit .ibkr import (
1723 build_ibkr_strategy_context ,
@@ -211,6 +217,74 @@ def _with_consecutive_loss_metadata(self, portfolio_snapshot: Any | None) -> Any
211217 metadata ["consecutive_losses" ] = int (streak )
212218 return replace (portfolio_snapshot , metadata = metadata )
213219
220+ def _build_capital_base_capabilities (
221+ self ,
222+ portfolio_snapshot : Any | None ,
223+ ) -> tuple [dict [str , Any ], str ]:
224+ """Attach strict v2 evidence only to an explicitly scoped IBKR snapshot."""
225+
226+ runtime_target = self .runtime_settings .runtime_target
227+ if portfolio_snapshot is None :
228+ return {}, "unavailable:portfolio_snapshot"
229+ if runtime_target is None :
230+ return {}, "unavailable:runtime_target"
231+ account_scope = str (runtime_target .account_scope or "" ).strip ()
232+ runtime_scope = str (
233+ runtime_target .service_name or runtime_target .deployment_selector or ""
234+ ).strip ()
235+ if not account_scope or not runtime_scope :
236+ return {}, "unavailable:runtime_scope"
237+ metadata = getattr (portfolio_snapshot , "metadata" , {})
238+ if not isinstance (metadata , Mapping ):
239+ return {}, "unavailable:portfolio_metadata"
240+ if metadata .get ("total_equity_source" ) != "broker_net_liquidation" :
241+ return {}, "unavailable:unverified_net_liquidation"
242+ source_digest = str (metadata .get ("source_digest_sha256" ) or "" ).strip ()
243+ if not source_digest :
244+ return {}, "unavailable:source_digest"
245+ try :
246+ capital_base = build_capital_base_snapshot (
247+ portfolio_snapshot ,
248+ account_scope = account_scope ,
249+ runtime_scope = runtime_scope ,
250+ strategy_scope = self .profile ,
251+ reported_currency = "USD" ,
252+ target_currency = "USD" ,
253+ fx_rate_to_target = 1.0 ,
254+ source_digest_sha256 = source_digest ,
255+ capital_scope = CapitalScope .ACCOUNT ,
256+ valuation_basis = CapitalValuationBasis .BROKER_ACCOUNT_NET_LIQUIDATION ,
257+ )
258+ binding = CapitalBaseBinding (
259+ account_scope = account_scope ,
260+ runtime_scope = runtime_scope ,
261+ strategy_scope = self .profile ,
262+ target_currency = "USD" ,
263+ capital_scope = CapitalScope .ACCOUNT ,
264+ valuation_basis = CapitalValuationBasis .BROKER_ACCOUNT_NET_LIQUIDATION ,
265+ )
266+ except (TypeError , ValueError ):
267+ return {}, "unavailable:invalid_capital_evidence"
268+ return {
269+ "capital_base" : capital_base ,
270+ "capital_base_binding" : binding ,
271+ }, "verified:broker_account_net_liquidation"
272+
273+ def _build_context_capabilities (
274+ self ,
275+ * ,
276+ ib : Any | None ,
277+ portfolio_snapshot : Any | None ,
278+ ) -> dict [str , Any ]:
279+ capabilities : dict [str , Any ] = {}
280+ if ib is not None :
281+ capabilities ["broker_client" ] = ib
282+ capital_base_capabilities , _status = self ._build_capital_base_capabilities (
283+ portfolio_snapshot
284+ )
285+ capabilities .update (capital_base_capabilities )
286+ return capabilities
287+
214288 def _prepare_portfolio_snapshot (
215289 self ,
216290 portfolio_snapshot : Any | None ,
@@ -624,9 +698,10 @@ def _build_strategy_context(
624698 market_inputs = market_inputs ,
625699 portfolio_snapshot = portfolio_snapshot ,
626700 )
627- capabilities = {}
628- if ib is not None :
629- capabilities ["broker_client" ] = ib
701+ capabilities = self ._build_context_capabilities (
702+ ib = ib ,
703+ portfolio_snapshot = portfolio_snapshot ,
704+ )
630705 return build_strategy_context_from_available_inputs (
631706 entrypoint = self .entrypoint ,
632707 runtime_adapter = context_adapter ,
@@ -756,6 +831,7 @@ def _evaluate_direct_market_data_strategy(
756831 )
757832 metadata = {
758833 "strategy_profile" : self .profile ,
834+ "capital_base_status" : self ._build_capital_base_capabilities (portfolio_snapshot )[1 ],
759835 "managed_symbols" : managed_symbols ,
760836 "status_icon" : self .status_icon ,
761837 "dry_run_only" : self .runtime_settings .dry_run_only ,
@@ -838,6 +914,7 @@ def _evaluate_market_data_strategy(
838914 )
839915 metadata = {
840916 "strategy_profile" : self .profile ,
917+ "capital_base_status" : self ._build_capital_base_capabilities (portfolio_snapshot )[1 ],
841918 "managed_symbols" : managed_symbols ,
842919 "status_icon" : self .status_icon ,
843920 "dry_run_only" : self .runtime_settings .dry_run_only ,
@@ -899,6 +976,13 @@ def _evaluate_value_target_strategy(
899976 current_holdings = current_holdings ,
900977 ib = ib ,
901978 )
979+ ctx = replace (
980+ ctx ,
981+ capabilities = self ._build_context_capabilities (
982+ ib = ib ,
983+ portfolio_snapshot = portfolio_snapshot ,
984+ ),
985+ )
902986 decision = self .entrypoint .evaluate (ctx )
903987 safe_haven_symbol = next (
904988 (position .symbol for position in decision .positions if position .role == "safe_haven" ),
@@ -916,6 +1000,7 @@ def _evaluate_value_target_strategy(
9161000 metadata = self ._enrich_portfolio_metadata (
9171001 {
9181002 "strategy_profile" : self .profile ,
1003+ "capital_base_status" : self ._build_capital_base_capabilities (portfolio_snapshot )[1 ],
9191004 "managed_symbols" : managed_symbols ,
9201005 "status_icon" : self .status_icon ,
9211006 "dry_run_only" : self .runtime_settings .dry_run_only ,
@@ -1044,9 +1129,10 @@ def build_context(request: FeatureSnapshotContextRequest):
10441129 available_inputs = dict (request .available_inputs )
10451130 if portfolio_snapshot is not None :
10461131 available_inputs [_PORTFOLIO_SNAPSHOT_INPUT ] = portfolio_snapshot
1047- capabilities = {}
1048- if ib is not None :
1049- capabilities ["broker_client" ] = ib
1132+ capabilities = self ._build_context_capabilities (
1133+ ib = ib ,
1134+ portfolio_snapshot = portfolio_snapshot ,
1135+ )
10501136 return build_strategy_context_from_available_inputs (
10511137 entrypoint = request .entrypoint ,
10521138 runtime_adapter = runtime_adapter ,
0 commit comments