Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions application/runtime_strategy_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,26 @@ def _bar_size_to_yfinance_interval(bar_size: str) -> str:
return "1d"


def _frame_column(frame: pd.DataFrame, name: str):
def _coerce_frame_selection_to_series(selection: Any) -> pd.Series | None:
if isinstance(selection, pd.Series):
return selection
if isinstance(selection, pd.DataFrame) and len(selection.columns) > 0:
return selection.iloc[:, 0]
return None


def _frame_column(frame: pd.DataFrame, name: str) -> pd.Series | None:
if name in frame.columns:
return frame[name]
series = _coerce_frame_selection_to_series(frame[name])
if series is not None:
return series
if isinstance(frame.columns, pd.MultiIndex):
expected = name.lower()
for column in frame.columns:
if any(str(part).lower() == expected for part in column):
return frame[column]
series = _coerce_frame_selection_to_series(frame[column])
if series is not None:
return series
return None


Expand Down
27 changes: 26 additions & 1 deletion tests/test_runtime_strategy_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pandas as pd

from application.runtime_strategy_adapters import build_runtime_strategy_adapters
from application.runtime_strategy_adapters import _coerce_yfinance_candles, build_runtime_strategy_adapters


def test_strategy_plugin_signals_are_loaded_reported_and_rendered():
Expand Down Expand Up @@ -98,6 +98,31 @@ def test_historical_candles_fall_back_when_ibkr_history_is_empty():
assert adapters.get_historical_candles("fake-ib", "QQQ") == fallback


def test_yfinance_candle_coercion_handles_single_symbol_multiindex_columns():
frame = pd.DataFrame(
[[100.0, 101.0, 102.0, 99.0, 1_000_000.0]],
index=pd.to_datetime(["2026-05-22"]),
columns=pd.MultiIndex.from_tuples(
[
("Open", "QQQ"),
("Close", "QQQ"),
("High", "QQQ"),
("Low", "QQQ"),
("Volume", "QQQ"),
]
),
)

candles = _coerce_yfinance_candles(frame)

assert len(candles) == 1
assert candles[0]["open"] == 100.0
assert candles[0]["close"] == 101.0
assert candles[0]["high"] == 102.0
assert candles[0]["low"] == 99.0
assert candles[0]["volume"] == 1_000_000.0


def test_strategy_plugin_true_crisis_builds_escalated_alert_message():
translations = {
"strategy_plugin_line": "plugin={plugin}|mode={mode}|route={route}|action={action}",
Expand Down