feat: add portfolio ownership model, auth guards, and migration
This commit is contained in:
+66
-16
@@ -2,7 +2,7 @@
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||
|
||||
from app.core.auth import Permission, authorize_request
|
||||
from app.core.auth import AuthContext, Permission, authorize_request
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.repositories.factory import get_portfolio_repository_from_settings
|
||||
from app.schemas.portfolio import (
|
||||
@@ -41,6 +41,28 @@ def _close_repository(repository: object) -> None:
|
||||
repository.close()
|
||||
|
||||
|
||||
def _ownership_required(settings: Settings) -> bool:
|
||||
return settings.db_enabled
|
||||
|
||||
|
||||
def _require_owner_context(auth_context: AuthContext) -> str:
|
||||
if auth_context.user_id is None:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Ownership context is required")
|
||||
return auth_context.user_id
|
||||
|
||||
|
||||
def _ensure_portfolio_owned(repository: object, portfolio_id: str, auth_context: AuthContext) -> None:
|
||||
if auth_context.permission == Permission.ADMIN:
|
||||
return
|
||||
owner_user_id = _require_owner_context(auth_context)
|
||||
try:
|
||||
portfolio = repository.get(portfolio_id)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
if portfolio.owner_user_id != owner_user_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Portfolio ownership mismatch")
|
||||
|
||||
|
||||
@router.get("", response_model=list[PortfolioListItem])
|
||||
def list_portfolios(
|
||||
request: Request,
|
||||
@@ -48,10 +70,13 @@ def list_portfolios(
|
||||
status_filter: str | None = None,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> list[PortfolioListItem]:
|
||||
authorize_request(Permission.READ, request, settings)
|
||||
auth_context = authorize_request(Permission.READ, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
return service.list_portfolios(broker=broker, status=status_filter)
|
||||
owner_user_id: str | None = None
|
||||
if _ownership_required(settings) and auth_context.permission != Permission.ADMIN:
|
||||
owner_user_id = _require_owner_context(auth_context)
|
||||
return service.list_portfolios(broker=broker, status=status_filter, owner_user_id=owner_user_id)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
|
||||
@@ -64,9 +89,11 @@ def get_portfolio(
|
||||
recent_trades: int = 5,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.READ, request, settings)
|
||||
auth_context = authorize_request(Permission.READ, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.get_portfolio_status(portfolio_id=portfolio_id, recent_trades=recent_trades)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -83,10 +110,13 @@ def create_portfolio(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
return service.create_portfolio(payload)
|
||||
owner_user_id = auth_context.user_id or "system"
|
||||
if _ownership_required(settings):
|
||||
owner_user_id = _require_owner_context(auth_context)
|
||||
return service.create_portfolio(payload, owner_user_id=owner_user_id)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
|
||||
@@ -99,9 +129,11 @@ def update_portfolio(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.update_portfolio(portfolio_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -115,9 +147,11 @@ def buy_portfolio_asset(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.buy(portfolio_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -131,9 +165,11 @@ def sell_portfolio_asset(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.sell(portfolio_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -147,9 +183,11 @@ def invest_buffer(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.invest(portfolio_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -163,9 +201,11 @@ def transfer_buffer(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.transfer(portfolio_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -179,9 +219,11 @@ def reset_portfolio(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioDetailResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.reset(portfolio_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
@@ -216,9 +258,11 @@ def create_comment(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> CommentResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.create_comment(portfolio_id, payload)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
@@ -233,9 +277,11 @@ def list_comments(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> list[CommentResponse]:
|
||||
authorize_request(Permission.READ, request, settings)
|
||||
auth_context = authorize_request(Permission.READ, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.list_comments(portfolio_id)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
@@ -252,9 +298,11 @@ def update_comment(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> CommentResponse:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.update_comment(portfolio_id, comment_id, payload)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
@@ -270,9 +318,11 @@ def delete_comment(
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> Response:
|
||||
authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
service.delete_comment(portfolio_id, comment_id)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
|
||||
@@ -72,6 +72,7 @@ class Portfolio:
|
||||
status: str
|
||||
name: str
|
||||
symbol: str
|
||||
owner_user_id: str | None = None
|
||||
cash_balance: float = 0.0
|
||||
asset_quantity: float = 0.0
|
||||
price: float = 0.0
|
||||
|
||||
+11
-1
@@ -27,12 +27,14 @@ class PortfolioDomainRepository(Protocol):
|
||||
status: str = "enabled",
|
||||
active_strategy_id: str | None = None,
|
||||
avabile_strategies: list[str] | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
initial_cash: float = 0.0,
|
||||
) -> Portfolio: ...
|
||||
def create_portfolio(
|
||||
self,
|
||||
portfolio_id: str,
|
||||
*,
|
||||
owner_user_id: str,
|
||||
broker: str,
|
||||
status: str,
|
||||
name: str,
|
||||
@@ -45,7 +47,13 @@ class PortfolioDomainRepository(Protocol):
|
||||
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]: ...
|
||||
def list_portfolios(
|
||||
self,
|
||||
*,
|
||||
broker: str | None = None,
|
||||
status: str | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
) -> list[Portfolio]: ...
|
||||
def get_strategy_set(self, id: str) -> StrategySet | None: ...
|
||||
def get_or_create_strategy_set(self, name: str, config: str) -> StrategySet: ...
|
||||
def save(
|
||||
@@ -103,6 +111,7 @@ def apply_buy(
|
||||
status=portfolio.status,
|
||||
name=portfolio.name,
|
||||
symbol=portfolio.symbol,
|
||||
owner_user_id=portfolio.owner_user_id,
|
||||
cash_balance=float(portfolio.cash_balance) - total_cost,
|
||||
asset_quantity=float(portfolio.asset_quantity) + quantity,
|
||||
price=price,
|
||||
@@ -164,6 +173,7 @@ def apply_sell(
|
||||
status=portfolio.status,
|
||||
name=portfolio.name,
|
||||
symbol=portfolio.symbol,
|
||||
owner_user_id=portfolio.owner_user_id,
|
||||
cash_balance=float(portfolio.cash_balance) + net_proceeds,
|
||||
asset_quantity=float(portfolio.asset_quantity) - quantity,
|
||||
price=price,
|
||||
|
||||
@@ -91,6 +91,7 @@ class PortfolioRepository(Protocol):
|
||||
status: str = "enabled",
|
||||
active_strategy_id: str | None = None,
|
||||
avabile_strategies: list[str] | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
initial_cash: float = 0.0,
|
||||
) -> Portfolio: ...
|
||||
|
||||
@@ -98,6 +99,7 @@ class PortfolioRepository(Protocol):
|
||||
self,
|
||||
portfolio_id: str,
|
||||
*,
|
||||
owner_user_id: str,
|
||||
broker: str,
|
||||
status: str,
|
||||
name: str,
|
||||
@@ -112,7 +114,13 @@ class PortfolioRepository(Protocol):
|
||||
|
||||
def update_portfolio(self, portfolio_id: str, **fields: object) -> Portfolio: ...
|
||||
|
||||
def list_portfolios(self, *, broker: str | None = None, status: str | None = None) -> list[Portfolio]: ...
|
||||
def list_portfolios(
|
||||
self,
|
||||
*,
|
||||
broker: str | None = None,
|
||||
status: str | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
) -> list[Portfolio]: ...
|
||||
|
||||
def get_or_create_strategy_set(self, name: str, config: str) -> StrategySet: ...
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ _SELECT_PORTFOLIO = """
|
||||
profit_buffer,
|
||||
active_strategy_id,
|
||||
avabile_strategies,
|
||||
owner_user_id,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_trade_at
|
||||
@@ -217,6 +218,7 @@ class PostgresPortfolioRepository:
|
||||
status TEXT NOT NULL DEFAULT 'enabled',
|
||||
name TEXT NOT NULL,
|
||||
symbol TEXT NOT NULL,
|
||||
owner_user_id TEXT,
|
||||
cash_balance DOUBLE PRECISION NOT NULL,
|
||||
asset_quantity DOUBLE PRECISION NOT NULL,
|
||||
asset_price DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
@@ -230,6 +232,7 @@ class PostgresPortfolioRepository:
|
||||
"""
|
||||
)
|
||||
cur.execute("ALTER TABLE portfolios ADD COLUMN IF NOT EXISTS name TEXT")
|
||||
cur.execute("ALTER TABLE portfolios ADD COLUMN IF NOT EXISTS owner_user_id TEXT")
|
||||
cur.execute("UPDATE portfolios SET name = symbol WHERE name IS NULL OR name = ''")
|
||||
cur.execute("ALTER TABLE portfolios ALTER COLUMN name SET NOT NULL")
|
||||
cur.execute(
|
||||
@@ -363,9 +366,10 @@ class PostgresPortfolioRepository:
|
||||
profit_buffer=float(row[8]),
|
||||
active_strategy_id=row[9] if len(row) > 9 else None,
|
||||
avabile_strategies=avabile_strategies,
|
||||
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,
|
||||
owner_user_id=row[11] if len(row) > 11 else None,
|
||||
created_at=row[12] if len(row) > 12 else None,
|
||||
updated_at=row[13] if len(row) > 13 else None,
|
||||
last_trade_at=row[14] if len(row) > 14 else None,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -726,6 +730,7 @@ class PostgresPortfolioRepository:
|
||||
status: str = "enabled",
|
||||
active_strategy_id: str | None = None,
|
||||
avabile_strategies: list[str] | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
initial_cash: float = 0.0,
|
||||
) -> Portfolio:
|
||||
self.ensure_schema()
|
||||
@@ -734,10 +739,10 @@ class PostgresPortfolioRepository:
|
||||
"""
|
||||
INSERT INTO portfolios (
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
owner_user_id, cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON CONFLICT (portfolio_id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
@@ -746,6 +751,7 @@ class PostgresPortfolioRepository:
|
||||
status,
|
||||
name or symbol,
|
||||
symbol,
|
||||
owner_user_id,
|
||||
float(initial_cash),
|
||||
0.0,
|
||||
0.0,
|
||||
@@ -762,6 +768,7 @@ class PostgresPortfolioRepository:
|
||||
self,
|
||||
portfolio_id: str,
|
||||
*,
|
||||
owner_user_id: str,
|
||||
broker: str,
|
||||
status: str,
|
||||
name: str,
|
||||
@@ -782,13 +789,13 @@ class PostgresPortfolioRepository:
|
||||
"""
|
||||
INSERT INTO portfolios (
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
owner_user_id, cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, owner_user_id, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(
|
||||
portfolio_id,
|
||||
@@ -796,6 +803,7 @@ class PostgresPortfolioRepository:
|
||||
status,
|
||||
name,
|
||||
symbol,
|
||||
owner_user_id,
|
||||
float(cash_balance),
|
||||
float(asset_quantity),
|
||||
float(asset_price),
|
||||
@@ -842,14 +850,20 @@ class PostgresPortfolioRepository:
|
||||
RETURNING
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, owner_user_id, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(*params, portfolio_id),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return self._row_to_portfolio(row)
|
||||
|
||||
def list_portfolios(self, *, broker: str | None = None, status: str | None = None) -> list[Portfolio]:
|
||||
def list_portfolios(
|
||||
self,
|
||||
*,
|
||||
broker: str | None = None,
|
||||
status: str | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
) -> list[Portfolio]:
|
||||
self.ensure_schema()
|
||||
query = _SELECT_PORTFOLIO
|
||||
clauses: list[str] = []
|
||||
@@ -860,6 +874,9 @@ class PostgresPortfolioRepository:
|
||||
if status is not None:
|
||||
clauses.append("status = %s")
|
||||
params.append(status)
|
||||
if owner_user_id is not None:
|
||||
clauses.append("owner_user_id = %s")
|
||||
params.append(owner_user_id)
|
||||
if clauses:
|
||||
query += "WHERE " + " AND ".join(clauses) + " "
|
||||
query += "ORDER BY updated_at DESC, portfolio_id ASC"
|
||||
@@ -1041,9 +1058,9 @@ class PostgresPortfolioRepository:
|
||||
last_trade_at = NULL
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol,
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, owner_user_id, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(portfolio_id,),
|
||||
)
|
||||
@@ -1080,9 +1097,9 @@ class PostgresPortfolioRepository:
|
||||
updated_at = NOW()
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol,
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, owner_user_id, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(updated_buffer, portfolio_id),
|
||||
)
|
||||
@@ -1149,9 +1166,9 @@ class PostgresPortfolioRepository:
|
||||
updated_at = NOW()
|
||||
WHERE portfolio_id = %s
|
||||
RETURNING
|
||||
portfolio_id, broker, status, symbol,
|
||||
portfolio_id, broker, status, name, symbol,
|
||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||
active_strategy_id, avabile_strategies, created_at, updated_at, last_trade_at
|
||||
active_strategy_id, avabile_strategies, owner_user_id, created_at, updated_at, last_trade_at
|
||||
""",
|
||||
(cash, buf, portfolio_id),
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ class PortfolioSummary(BaseModel):
|
||||
status: str
|
||||
name: str
|
||||
symbol: str
|
||||
owner_user_id: str | None = None
|
||||
cash_balance: float = 0.0
|
||||
asset_quantity: float = 0.0
|
||||
price: float = 0.0
|
||||
|
||||
@@ -49,6 +49,7 @@ def _to_portfolio_summary(portfolio: Portfolio):
|
||||
status=portfolio.status,
|
||||
name=portfolio.name,
|
||||
symbol=portfolio.symbol,
|
||||
owner_user_id=portfolio.owner_user_id,
|
||||
cash_balance=portfolio.cash_balance,
|
||||
asset_quantity=portfolio.asset_quantity,
|
||||
price=portfolio.price,
|
||||
@@ -137,8 +138,14 @@ class PortfolioService:
|
||||
def __init__(self, repository: PortfolioRepository) -> None:
|
||||
self._repository = repository
|
||||
|
||||
def list_portfolios(self, *, broker: str | None = None, status: str | None = None) -> list[PortfolioListItem]:
|
||||
portfolios = self._repository.list_portfolios(broker=broker, status=status)
|
||||
def list_portfolios(
|
||||
self,
|
||||
*,
|
||||
broker: str | None = None,
|
||||
status: str | None = None,
|
||||
owner_user_id: str | None = None,
|
||||
) -> list[PortfolioListItem]:
|
||||
portfolios = self._repository.list_portfolios(broker=broker, status=status, owner_user_id=owner_user_id)
|
||||
return [
|
||||
PortfolioListItem(
|
||||
**_to_portfolio_summary(portfolio).model_dump(),
|
||||
@@ -151,7 +158,12 @@ class PortfolioService:
|
||||
status_result = get_portfolio_status(self._repository, portfolio_id, recent_trades=recent_trades)
|
||||
return _to_detail_response(status_result)
|
||||
|
||||
def create_portfolio(self, payload: CreatePortfolioRequest) -> PortfolioDetailResponse:
|
||||
def create_portfolio(
|
||||
self,
|
||||
payload: CreatePortfolioRequest,
|
||||
*,
|
||||
owner_user_id: str = "system",
|
||||
) -> PortfolioDetailResponse:
|
||||
active_strategy_id = payload.active_strategy_id
|
||||
if active_strategy_id is None:
|
||||
strategy_set = self._repository.get_or_create_strategy_set(
|
||||
@@ -162,6 +174,7 @@ class PortfolioService:
|
||||
avabile_strategies = _merge_strategy_ids(active_strategy_id, payload.avabile_strategies)
|
||||
portfolio = self._repository.create_portfolio(
|
||||
payload.portfolio_id,
|
||||
owner_user_id=owner_user_id,
|
||||
broker=payload.broker,
|
||||
status=payload.status,
|
||||
name=payload.name or payload.symbol,
|
||||
|
||||
Reference in New Issue
Block a user