Skip to content

Commit 2f854e5

Browse files
committed
feat: Move stop prices into strategy metadata
1 parent ef6b3dc commit 2f854e5

11 files changed

Lines changed: 40 additions & 227 deletions

File tree

python/valuecell/agents/common/trading/_internal/coordinator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ async def run_once(self) -> DecisionCycleResult:
255255
history_records=history_records,
256256
digest=digest,
257257
portfolio_view=portfolio,
258-
stop_prices=stop_prices,
259258
)
260259

261260
def _create_trades(
@@ -483,6 +482,7 @@ def build_summary(
483482
# Use the portfolio view's total_value which now correctly reflects Equity
484483
# (whether simulated or synced from exchange)
485484
equity = float(view.total_value or 0.0)
485+
stop_prices = view.stop_prices
486486
except Exception:
487487
# Fallback to internal tracking if portfolio service is unavailable
488488
unrealized = float(self._unrealized_pnl or 0.0)
@@ -492,6 +492,7 @@ def build_summary(
492492
if self._request.trading_config.initial_capital is not None
493493
else 0.0
494494
)
495+
stop_prices = {}
495496

496497
# Keep internal state in sync (allow negative unrealized PnL)
497498
self._unrealized_pnl = float(unrealized)
@@ -516,6 +517,7 @@ def build_summary(
516517
unrealized_pnl_pct=unrealized_pnl_pct,
517518
pnl_pct=pnl_pct,
518519
total_value=equity,
520+
stop_prices=stop_prices,
519521
last_updated_ts=timestamp_ms,
520522
)
521523

python/valuecell/agents/common/trading/_internal/runtime.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,14 @@ async def create_strategy_runtime(
147147
"Initialized runtime initial capital from persisted snapshot for strategy_id=%s",
148148
strategy_id_override,
149149
)
150-
stop_prices = {
151-
stop_price.symbol: StopPrice(
152-
symbol=stop_price.symbol,
153-
stop_gain_price=stop_price.stop_gain_price,
154-
stop_loss_price=stop_price.stop_loss_price,
155-
)
156-
for stop_price in repo.get_stop_prices(strategy_id_override)
157-
}
150+
stop_prices = {}
151+
strategy = repo.get_strategy_by_strategy_id(strategy_id_override)
152+
if strategy and strategy.strategy_metadata:
153+
raw_stops = strategy.strategy_metadata.get("stop_prices", {})
154+
stop_prices = {
155+
symbol: StopPrice.model_validate(data)
156+
for symbol, data in raw_stops.items()
157+
}
158158
logger.info(
159159
"Initialized runtime stop prices {} from persisted snapshot for strategy_id {}",
160160
stop_prices,

python/valuecell/agents/common/trading/_internal/stream_controller.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,6 @@ def persist_cycle_results(self, result: DecisionCycleResult) -> None:
271271
"Persisted portfolio view for strategy={}", self.strategy_id
272272
)
273273

274-
ok = strategy_persistence.persist_stop_prices(
275-
self.strategy_id, result.stop_prices
276-
)
277-
if ok:
278-
logger.info("Persisted stop prices for strategy={}", self.strategy_id)
279-
280274
ok = strategy_persistence.persist_strategy_summary(result.strategy_summary)
281275
if ok:
282276
logger.info(

python/valuecell/agents/common/trading/decision/prompt_based/composer.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ async def compose(self, context: ComposeContext) -> ComposeResult:
9797
context.compose_id,
9898
plan.rationale,
9999
)
100-
return ComposeResult(instructions=[], rationale=plan.rationale)
100+
return ComposeResult(
101+
instructions=[],
102+
rationale=plan.rationale,
103+
stop_prices=plan.stop_prices,
104+
)
101105
except Exception as exc: # noqa: BLE001
102106
logger.error("LLM invocation failed: {}", exc)
103107
return ComposeResult(
@@ -252,9 +256,9 @@ async def _send_plan_to_discord(self, plan: TradePlanProposal) -> None:
252256
parts.append(f"{top_r}\n")
253257
if len(plan.stop_prices) > 0:
254258
parts.append("**Updated stop prices:**")
255-
for stop_price in plan.stop_prices:
259+
for symbol, stop_price in plan.stop_prices.items():
256260
parts.append(
257-
f"{stop_price.symbol}\tstop gain: {stop_price.stop_gain_price}\tstop loss: {stop_price.stop_loss_price}"
261+
f"{symbol}\tstop gain: {stop_price.stop_gain_price}\tstop loss: {stop_price.stop_loss_price}"
258262
)
259263
parts.append("")
260264

python/valuecell/agents/common/trading/models.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -551,8 +551,8 @@ class PortfolioView(BaseModel):
551551
),
552552
)
553553
stop_prices: Dict[str, "StopPrice"] = Field(
554-
default_factory=list,
555-
description="List of stop prices for existing positions and positions to open.",
554+
default_factory=dict,
555+
description="Dictionary of stop prices for existing positions and positions to open.",
556556
)
557557

558558

@@ -592,7 +592,6 @@ def derive_side_from_action(
592592

593593

594594
class StopPrice(BaseModel):
595-
symbol: str = Field(..., description="Exchange symbol, e.g., BTC/USDT")
596595
stop_gain_price: Optional[float] = Field(
597596
...,
598597
description="Stop gain price for this position.",
@@ -657,9 +656,9 @@ class TradePlanProposal(BaseModel):
657656
rationale: Optional[str] = Field(
658657
default=None, description="Optional natural language rationale"
659658
)
660-
stop_prices: List[StopPrice] = Field(
661-
default_factory=list,
662-
description="List of stop prices for existing positions and positions to open.",
659+
stop_prices: Dict[str, StopPrice] = Field(
660+
default_factory=dict,
661+
description="Map of ticker symbols to their respective stop prices",
663662
)
664663

665664

@@ -931,6 +930,10 @@ class StrategySummary(BaseModel):
931930
default=None,
932931
description="Total portfolio value (equity) including cash and positions",
933932
)
933+
stop_prices: Dict[str, StopPrice] = Field(
934+
default_factory=dict,
935+
description="Map of ticker symbols to their respective stop prices",
936+
)
934937
last_updated_ts: Optional[int] = Field(default=None)
935938

936939

@@ -954,7 +957,7 @@ class ComposeResult(BaseModel):
954957

955958
instructions: List[TradeInstruction]
956959
rationale: Optional[str] = None
957-
stop_prices: List[StopPrice] = []
960+
stop_prices: Dict[str, StopPrice] = {}
958961

959962

960963
class FeaturesPipelineResult(BaseModel):
@@ -977,4 +980,3 @@ class DecisionCycleResult:
977980
history_records: List[HistoryRecord]
978981
digest: TradeDigest
979982
portfolio_view: PortfolioView
980-
stop_prices: List[StopPrice]

python/valuecell/agents/common/trading/portfolio/in_memory.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
TradingMode,
1515
)
1616
from valuecell.agents.common.trading.utils import extract_price_map
17-
from valuecell.server.db.models import StrategyStopPrices
1817

1918
from .interfaces import BasePortfolioService
2019

@@ -43,7 +42,7 @@ def __init__(
4342
initial_positions: Dict[str, PositionSnapshot],
4443
trading_mode: TradingMode,
4544
market_type: MarketType,
46-
stop_prices: Dict[str, StrategyStopPrices],
45+
stop_prices: Dict[str, StopPrice],
4746
constraints: Optional[Constraints] = None,
4847
strategy_id: Optional[str] = None,
4948
) -> None:
@@ -93,21 +92,15 @@ def get_view(self) -> PortfolioView:
9392
pass
9493
return self._view
9594

96-
def update_stop_prices(self, stop_prices: List[StopPrice]) -> None:
97-
for stop_price in stop_prices:
98-
if stop_price.symbol in self._view.stop_prices:
99-
self._view.stop_prices[stop_price.symbol].stop_gain_price = (
100-
stop_price.stop_gain_price
101-
if stop_price.stop_gain_price is not None
102-
else self._view.stop_prices[stop_price.symbol].stop_gain_price
103-
)
104-
self._view.stop_prices[stop_price.symbol].stop_loss_price = (
105-
stop_price.stop_loss_price
106-
if stop_price.stop_loss_price is not None
107-
else self._view.stop_prices[stop_price.symbol].stop_loss_price
108-
)
95+
def update_stop_prices(self, stop_prices: Dict[str, StopPrice]) -> None:
96+
for symbol, new_stop in stop_prices.items():
97+
existing = self._view.stop_prices.get(symbol)
98+
if existing:
99+
update_data = new_stop.model_dump(exclude_unset=True, exclude_none=True)
100+
for key, value in update_data.items():
101+
setattr(existing, key, value)
109102
else:
110-
self._view.stop_prices[stop_price.symbol] = stop_price
103+
self._view.stop_prices[symbol] = new_stop
111104

112105
def apply_trades(
113106
self, trades: List[TradeHistoryEntry], market_features: List[FeatureVector]

python/valuecell/agents/common/trading/portfolio/interfaces.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from abc import ABC, abstractmethod
4-
from typing import List, Optional
4+
from typing import Dict, List, Optional
55

66
from valuecell.agents.common.trading.models import (
77
FeatureVector,
@@ -35,7 +35,7 @@ def apply_trades(
3535
"""
3636
raise NotImplementedError
3737

38-
def update_stop_prices(self, stop_prices: List[StopPrice]) -> None:
38+
def update_stop_prices(self, stop_prices: Dict[str, StopPrice]) -> None:
3939
"""Update the stop prices to the portfolio view.
4040
4141
Implementations that support state changes (paper trading, backtests)

python/valuecell/server/db/models/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from .strategy_holding import StrategyHolding
1818
from .strategy_instruction import StrategyInstruction
1919
from .strategy_portfolio import StrategyPortfolioView
20-
from .strategy_stop_price import StrategyStopPrices
2120
from .user_profile import ProfileCategory, UserProfile
2221
from .watchlist import Watchlist, WatchlistItem
2322

@@ -36,5 +35,4 @@
3635
"StrategyPortfolioView",
3736
"StrategyComposeCycle",
3837
"StrategyInstruction",
39-
"StrategyStopPrices",
4038
]

python/valuecell/server/db/models/strategy_stop_price.py

Lines changed: 0 additions & 94 deletions
This file was deleted.

0 commit comments

Comments
 (0)