feat: refactor portfolio strategy references
This commit is contained in:
@@ -74,7 +74,8 @@ def print_portfolio_detail(console: Console, detail: PortfolioDetailResponse) ->
|
||||
f"broker: {portfolio.broker}",
|
||||
f"status: {portfolio.status}",
|
||||
f"symbol: {portfolio.symbol}",
|
||||
f"strategy: {portfolio.strategy_name}",
|
||||
f"active_strategy_id: {portfolio.active_strategy_id or 'N/A'}",
|
||||
f"avabile_strategies: {', '.join(portfolio.avabile_strategies) if portfolio.avabile_strategies else 'N/A'}",
|
||||
f"cash_balance: {format_currency(portfolio.cash_balance)}",
|
||||
f"asset_quantity: {portfolio.asset_quantity:g}",
|
||||
f"price: {format_currency(portfolio.price)}",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -22,13 +22,12 @@ class Portfolio:
|
||||
broker: str
|
||||
status: str
|
||||
symbol: str
|
||||
strategy_name: str
|
||||
strategy_params: dict[str, Any] = field(default_factory=dict)
|
||||
cash_balance: float = 0.0
|
||||
asset_quantity: float = 0.0
|
||||
price: float = 0.0
|
||||
profit_buffer: float = 0.0
|
||||
current_strategy_set_id: str | None = None
|
||||
active_strategy_id: str | None = None
|
||||
avabile_strategies: list[str] | None = None
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
last_trade_at: datetime | None = None
|
||||
|
||||
+12
-14
@@ -24,8 +24,8 @@ class PortfolioDomainRepository(Protocol):
|
||||
*,
|
||||
broker: str = "kis",
|
||||
status: str = "enabled",
|
||||
strategy_name: str = "",
|
||||
strategy_params: dict | None = None,
|
||||
active_strategy_id: str | None = None,
|
||||
avabile_strategies: list[str] | None = None,
|
||||
initial_cash: float = 0.0,
|
||||
) -> Portfolio: ...
|
||||
def create_portfolio(
|
||||
@@ -35,12 +35,12 @@ class PortfolioDomainRepository(Protocol):
|
||||
broker: str,
|
||||
status: str,
|
||||
symbol: str,
|
||||
strategy_name: str,
|
||||
strategy_params: dict,
|
||||
cash_balance: float,
|
||||
asset_quantity: float,
|
||||
asset_price: float,
|
||||
profit_buffer: float,
|
||||
active_strategy_id: str | None,
|
||||
avabile_strategies: list[str] | None,
|
||||
) -> Portfolio: ...
|
||||
def update_portfolio(self, portfolio_id: str, **fields: object) -> Portfolio: ...
|
||||
def list_portfolios(self, *, broker: str | None = None, status: str | None = None) -> list[Portfolio]: ...
|
||||
@@ -100,13 +100,12 @@ def apply_buy(
|
||||
broker=portfolio.broker,
|
||||
status=portfolio.status,
|
||||
symbol=portfolio.symbol,
|
||||
strategy_name=portfolio.strategy_name,
|
||||
strategy_params=portfolio.strategy_params,
|
||||
cash_balance=float(portfolio.cash_balance) - total_cost,
|
||||
asset_quantity=float(portfolio.asset_quantity) + quantity,
|
||||
price=price,
|
||||
profit_buffer=float(portfolio.profit_buffer),
|
||||
current_strategy_set_id=portfolio.current_strategy_set_id,
|
||||
active_strategy_id=portfolio.active_strategy_id,
|
||||
avabile_strategies=portfolio.avabile_strategies,
|
||||
created_at=portfolio.created_at,
|
||||
updated_at=portfolio.updated_at,
|
||||
last_trade_at=portfolio.last_trade_at,
|
||||
@@ -121,7 +120,7 @@ def apply_buy(
|
||||
fee_amount=fee,
|
||||
tax_amount=tax,
|
||||
total_value_after=total_value_after,
|
||||
strategy_set_id=portfolio.current_strategy_set_id or "manual",
|
||||
strategy_set_id=portfolio.active_strategy_id or "manual",
|
||||
note=note,
|
||||
)
|
||||
return repo.get(portfolio_id)
|
||||
@@ -161,13 +160,12 @@ def apply_sell(
|
||||
broker=portfolio.broker,
|
||||
status=portfolio.status,
|
||||
symbol=portfolio.symbol,
|
||||
strategy_name=portfolio.strategy_name,
|
||||
strategy_params=portfolio.strategy_params,
|
||||
cash_balance=float(portfolio.cash_balance) + net_proceeds,
|
||||
asset_quantity=float(portfolio.asset_quantity) - quantity,
|
||||
price=price,
|
||||
profit_buffer=float(portfolio.profit_buffer),
|
||||
current_strategy_set_id=portfolio.current_strategy_set_id,
|
||||
active_strategy_id=portfolio.active_strategy_id,
|
||||
avabile_strategies=portfolio.avabile_strategies,
|
||||
created_at=portfolio.created_at,
|
||||
updated_at=portfolio.updated_at,
|
||||
last_trade_at=portfolio.last_trade_at,
|
||||
@@ -182,7 +180,7 @@ def apply_sell(
|
||||
fee_amount=fee,
|
||||
tax_amount=tax,
|
||||
total_value_after=total_value_after,
|
||||
strategy_set_id=portfolio.current_strategy_set_id or "manual",
|
||||
strategy_set_id=portfolio.active_strategy_id or "manual",
|
||||
note=note,
|
||||
)
|
||||
return repo.get(portfolio_id)
|
||||
@@ -265,8 +263,8 @@ def get_portfolio_status(
|
||||
portfolio = repo.get(portfolio_id)
|
||||
recent_entries = repo.list_recent_trades(portfolio_id, limit=recent_trades)
|
||||
strategy_set = None
|
||||
if portfolio.current_strategy_set_id:
|
||||
strategy_set = repo.get_strategy_set(portfolio.current_strategy_set_id)
|
||||
if portfolio.active_strategy_id:
|
||||
strategy_set = repo.get_strategy_set(portfolio.active_strategy_id)
|
||||
|
||||
live_quantity = None
|
||||
if live_source is not None:
|
||||
|
||||
@@ -19,8 +19,8 @@ class PortfolioRepository(Protocol):
|
||||
*,
|
||||
broker: str = "kis",
|
||||
status: str = "enabled",
|
||||
strategy_name: str = "",
|
||||
strategy_params: dict | None = None,
|
||||
active_strategy_id: str | None = None,
|
||||
avabile_strategies: list[str] | None = None,
|
||||
initial_cash: float = 0.0,
|
||||
) -> Portfolio: ...
|
||||
|
||||
@@ -31,12 +31,12 @@ class PortfolioRepository(Protocol):
|
||||
broker: str,
|
||||
status: str,
|
||||
symbol: str,
|
||||
strategy_name: str,
|
||||
strategy_params: dict,
|
||||
cash_balance: float,
|
||||
asset_quantity: float,
|
||||
asset_price: float,
|
||||
profit_buffer: float,
|
||||
active_strategy_id: str | None,
|
||||
avabile_strategies: list[str] | None,
|
||||
) -> Portfolio: ...
|
||||
|
||||
def update_portfolio(self, portfolio_id: str, **fields: object) -> Portfolio: ...
|
||||
|
||||
@@ -20,13 +20,12 @@ _SELECT_PORTFOLIO = """
|
||||
broker,
|
||||
status,
|
||||
symbol,
|
||||
strategy_name,
|
||||
strategy_params,
|
||||
cash_balance,
|
||||
asset_quantity,
|
||||
asset_price,
|
||||
profit_buffer,
|
||||
current_strategy_set_id,
|
||||
active_strategy_id,
|
||||
avabile_strategies,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_trade_at
|
||||
@@ -98,13 +97,12 @@ class PostgresPortfolioRepository:
|
||||
broker TEXT NOT NULL DEFAULT 'kis',
|
||||
status TEXT NOT NULL DEFAULT 'enabled',
|
||||
symbol TEXT NOT NULL,
|
||||
strategy_name TEXT NOT NULL DEFAULT '',
|
||||
strategy_params JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
cash_balance DOUBLE PRECISION NOT NULL,
|
||||
asset_quantity DOUBLE PRECISION NOT NULL,
|
||||
asset_price DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
profit_buffer DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
current_strategy_set_id TEXT DEFAULT NULL,
|
||||
active_strategy_id TEXT DEFAULT NULL,
|
||||
avabile_strategies TEXT[] DEFAULT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_trade_at TIMESTAMPTZ
|
||||
@@ -155,32 +153,28 @@ class PostgresPortfolioRepository:
|
||||
if row is None:
|
||||
raise ValueError("포트폴리오를 찾을 수 없습니다")
|
||||
|
||||
raw_params = row[5] if len(row) > 5 else {}
|
||||
if isinstance(raw_params, str):
|
||||
try:
|
||||
strategy_params = json.loads(raw_params) if raw_params else {}
|
||||
except json.JSONDecodeError:
|
||||
strategy_params = {}
|
||||
elif isinstance(raw_params, dict):
|
||||
strategy_params = raw_params
|
||||
raw_available = row[9] if len(row) > 9 else None
|
||||
if raw_available is None:
|
||||
avabile_strategies = None
|
||||
elif isinstance(raw_available, list):
|
||||
avabile_strategies = [str(item) for item in raw_available]
|
||||
else:
|
||||
strategy_params = {}
|
||||
avabile_strategies = [str(raw_available)]
|
||||
|
||||
return Portfolio(
|
||||
portfolio_id=row[0],
|
||||
broker=row[1] or "kis",
|
||||
status=row[2] or "enabled",
|
||||
symbol=row[3],
|
||||
strategy_name=row[4] or "",
|
||||
strategy_params=strategy_params,
|
||||
cash_balance=float(row[6]),
|
||||
asset_quantity=float(row[7]),
|
||||
price=float(row[8]),
|
||||
profit_buffer=float(row[9]),
|
||||
current_strategy_set_id=row[10] if len(row) > 10 else None,
|
||||
created_at=row[11] if len(row) > 11 else None,
|
||||
updated_at=row[12] if len(row) > 12 else None,
|
||||
last_trade_at=row[13] if len(row) > 13 else None,
|
||||
cash_balance=float(row[4]),
|
||||
asset_quantity=float(row[5]),
|
||||
price=float(row[6]),
|
||||
profit_buffer=float(row[7]),
|
||||
active_strategy_id=row[8] if len(row) > 8 else None,
|
||||
avabile_strategies=avabile_strategies,
|
||||
created_at=row[10] if len(row) > 10 else None,
|
||||
updated_at=row[11] if len(row) > 11 else None,
|
||||
last_trade_at=row[12] if len(row) > 12 else None,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -215,8 +209,8 @@ class PostgresPortfolioRepository:
|
||||
*,
|
||||
broker: str = "kis",
|
||||
status: str = "enabled",
|
||||
strategy_name: str = "",
|
||||
strategy_params: dict[str, Any] | None = None,
|
||||
active_strategy_id: str | None = None,
|
||||
avabile_strategies: list[str] | None = None,
|
||||
initial_cash: float = 0.0,
|
||||
) -> Portfolio:
|
||||
self.ensure_schema()
|
||||
@@ -224,10 +218,11 @@ class PostgresPortfolioRepository:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO portfolios (
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s, %s)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON CONFLICT (portfolio_id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
@@ -235,12 +230,12 @@ class PostgresPortfolioRepository:
|
||||
broker,
|
||||
status,
|
||||
symbol,
|
||||
strategy_name,
|
||||
json.dumps(strategy_params or {}),
|
||||
float(initial_cash),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
active_strategy_id,
|
||||
avabile_strategies,
|
||||
),
|
||||
)
|
||||
cur.execute(_SELECT_PORTFOLIO + "WHERE portfolio_id = %s", (portfolio_id,))
|
||||
@@ -254,12 +249,12 @@ class PostgresPortfolioRepository:
|
||||
broker: str,
|
||||
status: str,
|
||||
symbol: str,
|
||||
strategy_name: str,
|
||||
strategy_params: dict,
|
||||
cash_balance: float,
|
||||
asset_quantity: float,
|
||||
asset_price: float,
|
||||
profit_buffer: float,
|
||||
active_strategy_id: str | None,
|
||||
avabile_strategies: list[str] | None,
|
||||
) -> Portfolio:
|
||||
self.ensure_schema()
|
||||
with self._connect().cursor() as cur:
|
||||
@@ -269,25 +264,26 @@ class PostgresPortfolioRepository:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO portfolios (
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer
|
||||
) VALUES (%s, %s, %s, %s, %s, %s::jsonb, %s, %s, %s, %s)
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
current_strategy_set_id, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(
|
||||
portfolio_id,
|
||||
broker,
|
||||
status,
|
||||
symbol,
|
||||
strategy_name,
|
||||
json.dumps(strategy_params),
|
||||
float(cash_balance),
|
||||
float(asset_quantity),
|
||||
float(asset_price),
|
||||
float(profit_buffer),
|
||||
active_strategy_id,
|
||||
avabile_strategies,
|
||||
),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
@@ -299,19 +295,18 @@ class PostgresPortfolioRepository:
|
||||
"broker": "broker",
|
||||
"status": "status",
|
||||
"symbol": "symbol",
|
||||
"strategy_name": "strategy_name",
|
||||
"strategy_params": "strategy_params",
|
||||
"asset_price": "asset_price",
|
||||
"current_strategy_set_id": "current_strategy_set_id",
|
||||
"active_strategy_id": "active_strategy_id",
|
||||
"avabile_strategies": "avabile_strategies",
|
||||
}
|
||||
assignments: list[str] = []
|
||||
params: list[object] = []
|
||||
for key, value in fields.items():
|
||||
if value is None or key not in allowed:
|
||||
continue
|
||||
if key == "strategy_params":
|
||||
assignments.append("strategy_params = %s::jsonb")
|
||||
params.append(json.dumps(value))
|
||||
if key == "avabile_strategies":
|
||||
assignments.append("avabile_strategies = %s")
|
||||
params.append(value)
|
||||
else:
|
||||
assignments.append(f"{allowed[key]} = %s")
|
||||
params.append(value)
|
||||
@@ -326,9 +321,9 @@ class PostgresPortfolioRepository:
|
||||
SET {', '.join(assignments)}
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
current_strategy_set_id, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(*params, portfolio_id),
|
||||
)
|
||||
@@ -413,13 +408,12 @@ class PostgresPortfolioRepository:
|
||||
SET broker = %s,
|
||||
status = %s,
|
||||
symbol = %s,
|
||||
strategy_name = %s,
|
||||
strategy_params = %s::jsonb,
|
||||
cash_balance = %s,
|
||||
asset_quantity = %s,
|
||||
asset_price = %s,
|
||||
profit_buffer = %s,
|
||||
current_strategy_set_id = %s,
|
||||
active_strategy_id = %s,
|
||||
avabile_strategies = %s,
|
||||
updated_at = %s,
|
||||
last_trade_at = CASE WHEN %s IN ('BUY', 'SELL') THEN %s ELSE last_trade_at END
|
||||
WHERE portfolio_id = %s
|
||||
@@ -428,13 +422,12 @@ class PostgresPortfolioRepository:
|
||||
portfolio.broker,
|
||||
portfolio.status,
|
||||
portfolio.symbol,
|
||||
portfolio.strategy_name,
|
||||
json.dumps(portfolio.strategy_params),
|
||||
float(portfolio.cash_balance),
|
||||
float(portfolio.asset_quantity),
|
||||
float(current_price),
|
||||
float(portfolio.profit_buffer),
|
||||
portfolio.current_strategy_set_id,
|
||||
portfolio.active_strategy_id,
|
||||
portfolio.avabile_strategies,
|
||||
trade_time,
|
||||
action,
|
||||
trade_time,
|
||||
@@ -496,14 +489,14 @@ class PostgresPortfolioRepository:
|
||||
asset_quantity = 0,
|
||||
asset_price = 0,
|
||||
profit_buffer = 0,
|
||||
current_strategy_set_id = NULL,
|
||||
active_strategy_id = NULL,
|
||||
updated_at = NOW(),
|
||||
last_trade_at = NULL
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
current_strategy_set_id, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(portfolio_id,),
|
||||
)
|
||||
@@ -540,9 +533,9 @@ class PostgresPortfolioRepository:
|
||||
updated_at = NOW()
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
current_strategy_set_id, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(updated_buffer, portfolio_id),
|
||||
)
|
||||
@@ -609,9 +602,9 @@ class PostgresPortfolioRepository:
|
||||
updated_at = NOW()
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol, strategy_name, strategy_params,
|
||||
portfolio_id, broker, status, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
current_strategy_set_id, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(cash, buf, portfolio_id),
|
||||
)
|
||||
|
||||
@@ -36,8 +36,6 @@ class PortfolioBase(BaseModel):
|
||||
broker: str = "kis"
|
||||
status: str = "enabled"
|
||||
symbol: str
|
||||
strategy_name: str = "target_value"
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class PortfolioSummary(BaseModel):
|
||||
@@ -45,13 +43,12 @@ class PortfolioSummary(BaseModel):
|
||||
broker: str
|
||||
status: str
|
||||
symbol: str
|
||||
strategy_name: str
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
cash_balance: float = 0.0
|
||||
asset_quantity: float = 0.0
|
||||
price: float = 0.0
|
||||
profit_buffer: float = 0.0
|
||||
current_strategy_set_id: str | None = None
|
||||
active_strategy_id: str | None = None
|
||||
avabile_strategies: list[str] | None = None
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
last_trade_at: datetime | None = None
|
||||
@@ -70,6 +67,10 @@ class PortfolioDetailResponse(BaseModel):
|
||||
|
||||
class CreatePortfolioRequest(PortfolioBase):
|
||||
portfolio_id: str
|
||||
strategy_name: str = "target_value"
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
active_strategy_id: str | None = None
|
||||
avabile_strategies: list[str] | None = None
|
||||
cash_balance: float = Field(default=0.0, ge=0.0)
|
||||
asset_quantity: float = Field(default=0.0, ge=0.0)
|
||||
asset_price: float = Field(default=0.0, ge=0.0)
|
||||
@@ -82,6 +83,8 @@ class UpdatePortfolioRequest(BaseModel):
|
||||
symbol: str | None = None
|
||||
strategy_name: str | None = None
|
||||
strategy_params: dict[str, Any] | None = None
|
||||
active_strategy_id: str | None = None
|
||||
avabile_strategies: list[str] | None = None
|
||||
asset_price: float | None = Field(default=None, ge=0.0)
|
||||
|
||||
|
||||
|
||||
@@ -45,19 +45,33 @@ def _to_portfolio_summary(portfolio: Portfolio):
|
||||
broker=portfolio.broker,
|
||||
status=portfolio.status,
|
||||
symbol=portfolio.symbol,
|
||||
strategy_name=portfolio.strategy_name,
|
||||
strategy_params=portfolio.strategy_params,
|
||||
cash_balance=portfolio.cash_balance,
|
||||
asset_quantity=portfolio.asset_quantity,
|
||||
price=portfolio.price,
|
||||
profit_buffer=portfolio.profit_buffer,
|
||||
current_strategy_set_id=portfolio.current_strategy_set_id,
|
||||
active_strategy_id=portfolio.active_strategy_id,
|
||||
avabile_strategies=portfolio.avabile_strategies,
|
||||
created_at=portfolio.created_at,
|
||||
updated_at=portfolio.updated_at,
|
||||
last_trade_at=portfolio.last_trade_at,
|
||||
)
|
||||
|
||||
|
||||
def _parse_strategy_config(config: str) -> dict[str, object]:
|
||||
try:
|
||||
parsed = json.loads(config) if config else {}
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
return parsed if isinstance(parsed, dict) else {}
|
||||
|
||||
|
||||
def _merge_strategy_ids(active_strategy_id: str | None, strategy_ids: list[str] | None) -> list[str] | None:
|
||||
normalized = list(strategy_ids or [])
|
||||
if active_strategy_id and active_strategy_id not in normalized:
|
||||
normalized.append(active_strategy_id)
|
||||
return normalized or None
|
||||
|
||||
|
||||
def _to_strategy_set_response(strategy_set: StrategySet | None):
|
||||
from app.schemas.portfolio import StrategySetResponse
|
||||
|
||||
@@ -124,39 +138,68 @@ class PortfolioService:
|
||||
return _to_detail_response(status_result)
|
||||
|
||||
def create_portfolio(self, payload: CreatePortfolioRequest) -> PortfolioDetailResponse:
|
||||
strategy_set = self._repository.get_or_create_strategy_set(
|
||||
payload.strategy_name,
|
||||
json.dumps(payload.strategy_params, sort_keys=True, ensure_ascii=True),
|
||||
)
|
||||
active_strategy_id = payload.active_strategy_id
|
||||
if active_strategy_id is None:
|
||||
strategy_set = self._repository.get_or_create_strategy_set(
|
||||
payload.strategy_name,
|
||||
json.dumps(payload.strategy_params, sort_keys=True, ensure_ascii=True),
|
||||
)
|
||||
active_strategy_id = strategy_set.id
|
||||
avabile_strategies = _merge_strategy_ids(active_strategy_id, payload.avabile_strategies)
|
||||
portfolio = self._repository.create_portfolio(
|
||||
payload.portfolio_id,
|
||||
broker=payload.broker,
|
||||
status=payload.status,
|
||||
symbol=payload.symbol,
|
||||
strategy_name=payload.strategy_name,
|
||||
strategy_params=payload.strategy_params,
|
||||
cash_balance=payload.cash_balance,
|
||||
asset_quantity=payload.asset_quantity,
|
||||
asset_price=payload.asset_price,
|
||||
profit_buffer=payload.profit_buffer,
|
||||
)
|
||||
portfolio = self._repository.update_portfolio(
|
||||
payload.portfolio_id,
|
||||
current_strategy_set_id=strategy_set.id,
|
||||
active_strategy_id=active_strategy_id,
|
||||
avabile_strategies=avabile_strategies,
|
||||
)
|
||||
return self.get_portfolio_status(portfolio_id=portfolio.portfolio_id, recent_trades=5)
|
||||
|
||||
def update_portfolio(self, portfolio_id: str, payload: UpdatePortfolioRequest) -> PortfolioDetailResponse:
|
||||
fields = payload.model_dump(exclude_none=True)
|
||||
current = self._repository.get(portfolio_id)
|
||||
|
||||
if "strategy_name" in fields or "strategy_params" in fields:
|
||||
current = self._repository.get(portfolio_id)
|
||||
strategy_name = fields.get("strategy_name", current.strategy_name)
|
||||
strategy_params = fields.get("strategy_params", current.strategy_params)
|
||||
current_strategy_set = None
|
||||
if current.active_strategy_id:
|
||||
current_strategy_set = self._repository.get_strategy_set(current.active_strategy_id)
|
||||
|
||||
strategy_name = fields.get("strategy_name")
|
||||
if strategy_name is None and current_strategy_set is not None:
|
||||
strategy_name = current_strategy_set.name
|
||||
if strategy_name is None:
|
||||
raise ValueError("strategy_name is required when no active strategy is set")
|
||||
|
||||
strategy_params = fields.get("strategy_params")
|
||||
if strategy_params is None and current_strategy_set is not None:
|
||||
strategy_params = _parse_strategy_config(current_strategy_set.config)
|
||||
if strategy_params is None:
|
||||
strategy_params = {}
|
||||
|
||||
strategy_set = self._repository.get_or_create_strategy_set(
|
||||
strategy_name,
|
||||
json.dumps(strategy_params, sort_keys=True, ensure_ascii=True),
|
||||
)
|
||||
fields["current_strategy_set_id"] = strategy_set.id
|
||||
fields["active_strategy_id"] = strategy_set.id
|
||||
fields["avabile_strategies"] = _merge_strategy_ids(
|
||||
strategy_set.id,
|
||||
fields.get("avabile_strategies", current.avabile_strategies),
|
||||
)
|
||||
|
||||
elif "active_strategy_id" in fields:
|
||||
fields["avabile_strategies"] = _merge_strategy_ids(
|
||||
fields.get("active_strategy_id"),
|
||||
fields.get("avabile_strategies", current.avabile_strategies),
|
||||
)
|
||||
|
||||
fields.pop("strategy_name", None)
|
||||
fields.pop("strategy_params", None)
|
||||
|
||||
self._repository.update_portfolio(portfolio_id, **fields)
|
||||
return self.get_portfolio_status(portfolio_id=portfolio_id, recent_trades=5)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user