Skip to content

Commit 332ea32

Browse files
authored
Filter strategy portfolio equity
1 parent d6e3781 commit 332ea32

2 files changed

Lines changed: 85 additions & 3 deletions

File tree

application/rebalance_service.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,32 @@ def _snapshot_to_portfolio_view(snapshot) -> tuple[dict[str, dict[str, float | i
528528
return positions, account_values
529529

530530

531+
def _strategy_portfolio_view(positions, account_values, strategy_symbols):
532+
normalized_symbols = {
533+
str(symbol).strip().upper()
534+
for symbol in strategy_symbols or ()
535+
if str(symbol).strip()
536+
}
537+
if not normalized_symbols:
538+
return positions, account_values
539+
540+
filtered_positions = {
541+
symbol: details
542+
for symbol, details in dict(positions or {}).items()
543+
if str(symbol).strip().upper() in normalized_symbols
544+
}
545+
strategy_market_value = sum(
546+
float(details.get("quantity") or 0.0) * float(details.get("avg_cost") or 0.0)
547+
for details in filtered_positions.values()
548+
)
549+
buying_power = float(dict(account_values or {}).get("buying_power") or 0.0)
550+
filtered_account_values = {
551+
**dict(account_values or {}),
552+
"equity": buying_power + strategy_market_value,
553+
}
554+
return filtered_positions, filtered_account_values
555+
556+
531557
def run_strategy_core(
532558
*,
533559
runtime: IBKRRebalanceRuntime | None = None,
@@ -582,6 +608,16 @@ def run_strategy_core(
582608
signal_metadata = {}
583609
allocation = _resolve_weight_allocation(signal_metadata, required=target_weights is not None)
584610
resolved_target_weights = dict(allocation.get("targets") or {}) if target_weights is not None else None
611+
strategy_symbols = tuple(
612+
allocation.get("strategy_symbols")
613+
or signal_metadata.get("managed_symbols")
614+
or ()
615+
)
616+
positions, account_values = _strategy_portfolio_view(
617+
positions,
618+
account_values,
619+
strategy_symbols,
620+
)
585621
signal_metadata = dict(signal_metadata or {})
586622
signal_metadata["signal_snapshot"] = build_signal_snapshot(
587623
platform="ibkr",

strategy_runtime.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
StrategyRuntimeAdapter,
2828
apply_runtime_policy_to_runtime_config,
2929
build_execution_timing_metadata,
30+
build_account_state_from_portfolio_snapshot,
31+
build_portfolio_snapshot_from_account_state,
3032
build_strategy_context_from_available_inputs,
3133
build_strategy_evaluation_inputs,
3234
)
@@ -355,6 +357,47 @@ def _market_history_symbols(self) -> tuple[str, ...]:
355357
)
356358
)
357359

360+
def _configured_strategy_symbols(self, *, include_ranking_pool: bool = False) -> tuple[str, ...]:
361+
candidates: list[str] = []
362+
raw_managed = self.merged_runtime_config.get("managed_symbols", ())
363+
if isinstance(raw_managed, str):
364+
raw_managed = raw_managed.replace(";", ",").split(",")
365+
candidates.extend(str(symbol) for symbol in raw_managed or ())
366+
if include_ranking_pool:
367+
raw_pool = self.merged_runtime_config.get("ranking_pool", ())
368+
if isinstance(raw_pool, str):
369+
raw_pool = raw_pool.replace(";", ",").split(",")
370+
candidates.extend(str(symbol) for symbol in raw_pool or ())
371+
safe_haven_symbol = str(self.merged_runtime_config.get("safe_haven") or "").strip()
372+
if safe_haven_symbol and candidates:
373+
candidates.append(safe_haven_symbol)
374+
return tuple(
375+
dict.fromkeys(
376+
symbol.strip().upper()
377+
for symbol in candidates
378+
if symbol.strip()
379+
)
380+
)
381+
382+
def _project_portfolio_snapshot(self, portfolio_snapshot: Any | None, strategy_symbols) -> Any | None:
383+
if portfolio_snapshot is None or not strategy_symbols:
384+
return portfolio_snapshot
385+
if not hasattr(portfolio_snapshot, "positions"):
386+
return portfolio_snapshot
387+
account_state = build_account_state_from_portfolio_snapshot(
388+
portfolio_snapshot,
389+
strategy_symbols=strategy_symbols,
390+
)
391+
account_state["total_strategy_equity"] = float(account_state["available_cash"]) + sum(
392+
float(value) for value in dict(account_state["market_values"]).values()
393+
)
394+
return build_portfolio_snapshot_from_account_state(
395+
account_state,
396+
strategy_symbols=strategy_symbols,
397+
as_of=getattr(portfolio_snapshot, "as_of", None),
398+
metadata=getattr(portfolio_snapshot, "metadata", {}) or {},
399+
)
400+
358401
def _build_market_history_inputs(
359402
self,
360403
ib,
@@ -481,6 +524,10 @@ def _evaluate_market_data_strategy(
481524
ib,
482525
required=requires_portfolio,
483526
)
527+
portfolio_snapshot = self._project_portfolio_snapshot(
528+
portfolio_snapshot,
529+
self._configured_strategy_symbols(include_ranking_pool=True),
530+
)
484531
portfolio_snapshot = self._attach_strategy_plugin_metadata(portfolio_snapshot, strategy_plugin_signals)
485532
option_chains = self._fetch_option_chains_for_runtime(ib, runtime_config, portfolio_snapshot)
486533
if option_chains:
@@ -550,7 +597,9 @@ def _evaluate_value_target_strategy(
550597
runtime_config = dict(self.runtime_config)
551598
runtime_config.setdefault("translator", translator)
552599
apply_runtime_policy_to_runtime_config(runtime_config, self.runtime_adapter)
600+
managed_symbols = self._configured_strategy_symbols()
553601
portfolio_snapshot = self._fetch_portfolio_snapshot_for_context(ib, required=True)
602+
portfolio_snapshot = self._project_portfolio_snapshot(portfolio_snapshot, managed_symbols)
554603
portfolio_snapshot = self._attach_strategy_plugin_metadata(portfolio_snapshot, strategy_plugin_signals)
555604
option_chains = self._fetch_option_chains_for_runtime(ib, runtime_config, portfolio_snapshot)
556605
if option_chains:
@@ -571,9 +620,6 @@ def _evaluate_value_target_strategy(
571620
ib=ib,
572621
)
573622
decision = self.entrypoint.evaluate(ctx)
574-
managed_symbols = tuple(
575-
str(symbol) for symbol in self.merged_runtime_config.get("managed_symbols", ())
576-
)
577623
safe_haven_symbol = next(
578624
(position.symbol for position in decision.positions if position.role == "safe_haven"),
579625
None,

0 commit comments

Comments
 (0)