ci: fix lint baseline and stabilize failing main tests
This commit is contained in:
@@ -8,12 +8,12 @@ Defines the data contracts for the proactive strategy system:
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, date, datetime
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class ScenarioAction(str, Enum):
|
||||
class ScenarioAction(StrEnum):
|
||||
"""Actions that can be taken by scenarios."""
|
||||
|
||||
BUY = "BUY"
|
||||
@@ -22,7 +22,7 @@ class ScenarioAction(str, Enum):
|
||||
REDUCE_ALL = "REDUCE_ALL"
|
||||
|
||||
|
||||
class MarketOutlook(str, Enum):
|
||||
class MarketOutlook(StrEnum):
|
||||
"""AI's assessment of market direction."""
|
||||
|
||||
BULLISH = "bullish"
|
||||
@@ -32,7 +32,7 @@ class MarketOutlook(str, Enum):
|
||||
BEARISH = "bearish"
|
||||
|
||||
|
||||
class PlaybookStatus(str, Enum):
|
||||
class PlaybookStatus(StrEnum):
|
||||
"""Lifecycle status of a playbook."""
|
||||
|
||||
PENDING = "pending"
|
||||
|
||||
@@ -6,7 +6,6 @@ Designed for the pre-market strategy system (one playbook per market per day).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sqlite3
|
||||
from datetime import date
|
||||
@@ -53,8 +52,10 @@ class PlaybookStore:
|
||||
row_id = cursor.lastrowid or 0
|
||||
logger.info(
|
||||
"Saved playbook for %s/%s (%d stocks, %d scenarios)",
|
||||
playbook.date, playbook.market,
|
||||
playbook.stock_count, playbook.scenario_count,
|
||||
playbook.date,
|
||||
playbook.market,
|
||||
playbook.stock_count,
|
||||
playbook.scenario_count,
|
||||
)
|
||||
return row_id
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ State progression is monotonic (promotion-only) except terminal EXITED.
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class PositionState(str, Enum):
|
||||
class PositionState(StrEnum):
|
||||
HOLDING = "HOLDING"
|
||||
BE_LOCK = "BE_LOCK"
|
||||
ARMED = "ARMED"
|
||||
@@ -40,12 +40,7 @@ def evaluate_exit_first(inp: StateTransitionInput) -> bool:
|
||||
|
||||
EXITED must be evaluated before any promotion.
|
||||
"""
|
||||
return (
|
||||
inp.hard_stop_hit
|
||||
or inp.trailing_stop_hit
|
||||
or inp.model_exit_signal
|
||||
or inp.be_lock_threat
|
||||
)
|
||||
return inp.hard_stop_hit or inp.trailing_stop_hit or inp.model_exit_signal or inp.be_lock_threat
|
||||
|
||||
|
||||
def promote_state(current: PositionState, inp: StateTransitionInput) -> PositionState:
|
||||
|
||||
@@ -124,12 +124,14 @@ class PreMarketPlanner:
|
||||
|
||||
# 4. Parse response
|
||||
playbook = self._parse_response(
|
||||
decision.rationale, today, market, candidates, cross_market,
|
||||
decision.rationale,
|
||||
today,
|
||||
market,
|
||||
candidates,
|
||||
cross_market,
|
||||
current_holdings=current_holdings,
|
||||
)
|
||||
playbook_with_tokens = playbook.model_copy(
|
||||
update={"token_count": decision.token_count}
|
||||
)
|
||||
playbook_with_tokens = playbook.model_copy(update={"token_count": decision.token_count})
|
||||
logger.info(
|
||||
"Generated playbook for %s: %d stocks, %d scenarios, %d tokens",
|
||||
market,
|
||||
@@ -146,7 +148,9 @@ class PreMarketPlanner:
|
||||
return self._empty_playbook(today, market)
|
||||
|
||||
def build_cross_market_context(
|
||||
self, target_market: str, today: date | None = None,
|
||||
self,
|
||||
target_market: str,
|
||||
today: date | None = None,
|
||||
) -> CrossMarketContext | None:
|
||||
"""Build cross-market context from the other market's L6 data.
|
||||
|
||||
@@ -192,7 +196,9 @@ class PreMarketPlanner:
|
||||
)
|
||||
|
||||
def build_self_market_scorecard(
|
||||
self, market: str, today: date | None = None,
|
||||
self,
|
||||
market: str,
|
||||
today: date | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Build previous-day scorecard for the same market."""
|
||||
if today is None:
|
||||
@@ -320,18 +326,18 @@ class PreMarketPlanner:
|
||||
f"{context_text}\n"
|
||||
f"## Instructions\n"
|
||||
f"Return a JSON object with this exact structure:\n"
|
||||
f'{{\n'
|
||||
f"{{\n"
|
||||
f' "market_outlook": "bullish|neutral_to_bullish|neutral'
|
||||
f'|neutral_to_bearish|bearish",\n'
|
||||
f' "global_rules": [\n'
|
||||
f' {{"condition": "portfolio_pnl_pct < -2.0",'
|
||||
f' "action": "REDUCE_ALL", "rationale": "..."}}\n'
|
||||
f' ],\n'
|
||||
f" ],\n"
|
||||
f' "stocks": [\n'
|
||||
f' {{\n'
|
||||
f" {{\n"
|
||||
f' "stock_code": "...",\n'
|
||||
f' "scenarios": [\n'
|
||||
f' {{\n'
|
||||
f" {{\n"
|
||||
f' "condition": {{"rsi_below": 30, "volume_ratio_above": 2.0,'
|
||||
f' "unrealized_pnl_pct_above": 3.0, "holding_days_above": 5}},\n'
|
||||
f' "action": "BUY|SELL|HOLD",\n'
|
||||
@@ -340,11 +346,11 @@ class PreMarketPlanner:
|
||||
f' "stop_loss_pct": -2.0,\n'
|
||||
f' "take_profit_pct": 3.0,\n'
|
||||
f' "rationale": "..."\n'
|
||||
f' }}\n'
|
||||
f' ]\n'
|
||||
f' }}\n'
|
||||
f' ]\n'
|
||||
f'}}\n\n'
|
||||
f" }}\n"
|
||||
f" ]\n"
|
||||
f" }}\n"
|
||||
f" ]\n"
|
||||
f"}}\n\n"
|
||||
f"Rules:\n"
|
||||
f"- Max {max_scenarios} scenarios per stock\n"
|
||||
f"- Candidates list is the primary source for BUY candidates\n"
|
||||
@@ -575,8 +581,7 @@ class PreMarketPlanner:
|
||||
stop_loss_pct=-3.0,
|
||||
take_profit_pct=5.0,
|
||||
rationale=(
|
||||
f"Rule-based BUY: oversold signal, "
|
||||
f"RSI={c.rsi:.0f} (fallback planner)"
|
||||
f"Rule-based BUY: oversold signal, RSI={c.rsi:.0f} (fallback planner)"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -107,7 +107,9 @@ class ScenarioEngine:
|
||||
# 2. Find stock playbook
|
||||
stock_pb = playbook.get_stock_playbook(stock_code)
|
||||
if stock_pb is None:
|
||||
logger.debug("No playbook for %s — defaulting to %s", stock_code, playbook.default_action)
|
||||
logger.debug(
|
||||
"No playbook for %s — defaulting to %s", stock_code, playbook.default_action
|
||||
)
|
||||
return ScenarioMatch(
|
||||
stock_code=stock_code,
|
||||
matched_scenario=None,
|
||||
@@ -135,7 +137,9 @@ class ScenarioEngine:
|
||||
)
|
||||
|
||||
# 4. No match — default action
|
||||
logger.debug("No scenario matched for %s — defaulting to %s", stock_code, playbook.default_action)
|
||||
logger.debug(
|
||||
"No scenario matched for %s — defaulting to %s", stock_code, playbook.default_action
|
||||
)
|
||||
return ScenarioMatch(
|
||||
stock_code=stock_code,
|
||||
matched_scenario=None,
|
||||
@@ -198,17 +202,27 @@ class ScenarioEngine:
|
||||
checks.append(price is not None and price < condition.price_below)
|
||||
|
||||
price_change_pct = self._safe_float(market_data.get("price_change_pct"))
|
||||
if condition.price_change_pct_above is not None or condition.price_change_pct_below is not None:
|
||||
if (
|
||||
condition.price_change_pct_above is not None
|
||||
or condition.price_change_pct_below is not None
|
||||
):
|
||||
if "price_change_pct" not in market_data:
|
||||
self._warn_missing_key("price_change_pct")
|
||||
if condition.price_change_pct_above is not None:
|
||||
checks.append(price_change_pct is not None and price_change_pct > condition.price_change_pct_above)
|
||||
checks.append(
|
||||
price_change_pct is not None and price_change_pct > condition.price_change_pct_above
|
||||
)
|
||||
if condition.price_change_pct_below is not None:
|
||||
checks.append(price_change_pct is not None and price_change_pct < condition.price_change_pct_below)
|
||||
checks.append(
|
||||
price_change_pct is not None and price_change_pct < condition.price_change_pct_below
|
||||
)
|
||||
|
||||
# Position-aware conditions
|
||||
unrealized_pnl_pct = self._safe_float(market_data.get("unrealized_pnl_pct"))
|
||||
if condition.unrealized_pnl_pct_above is not None or condition.unrealized_pnl_pct_below is not None:
|
||||
if (
|
||||
condition.unrealized_pnl_pct_above is not None
|
||||
or condition.unrealized_pnl_pct_below is not None
|
||||
):
|
||||
if "unrealized_pnl_pct" not in market_data:
|
||||
self._warn_missing_key("unrealized_pnl_pct")
|
||||
if condition.unrealized_pnl_pct_above is not None:
|
||||
@@ -227,15 +241,9 @@ class ScenarioEngine:
|
||||
if "holding_days" not in market_data:
|
||||
self._warn_missing_key("holding_days")
|
||||
if condition.holding_days_above is not None:
|
||||
checks.append(
|
||||
holding_days is not None
|
||||
and holding_days > condition.holding_days_above
|
||||
)
|
||||
checks.append(holding_days is not None and holding_days > condition.holding_days_above)
|
||||
if condition.holding_days_below is not None:
|
||||
checks.append(
|
||||
holding_days is not None
|
||||
and holding_days < condition.holding_days_below
|
||||
)
|
||||
checks.append(holding_days is not None and holding_days < condition.holding_days_below)
|
||||
|
||||
return len(checks) > 0 and all(checks)
|
||||
|
||||
@@ -295,9 +303,15 @@ class ScenarioEngine:
|
||||
details["volume_ratio"] = self._safe_float(market_data.get("volume_ratio"))
|
||||
if condition.price_above is not None or condition.price_below is not None:
|
||||
details["current_price"] = self._safe_float(market_data.get("current_price"))
|
||||
if condition.price_change_pct_above is not None or condition.price_change_pct_below is not None:
|
||||
if (
|
||||
condition.price_change_pct_above is not None
|
||||
or condition.price_change_pct_below is not None
|
||||
):
|
||||
details["price_change_pct"] = self._safe_float(market_data.get("price_change_pct"))
|
||||
if condition.unrealized_pnl_pct_above is not None or condition.unrealized_pnl_pct_below is not None:
|
||||
if (
|
||||
condition.unrealized_pnl_pct_above is not None
|
||||
or condition.unrealized_pnl_pct_below is not None
|
||||
):
|
||||
details["unrealized_pnl_pct"] = self._safe_float(market_data.get("unrealized_pnl_pct"))
|
||||
if condition.holding_days_above is not None or condition.holding_days_below is not None:
|
||||
details["holding_days"] = self._safe_float(market_data.get("holding_days"))
|
||||
|
||||
Reference in New Issue
Block a user