Add ProofLine criteria documentation and Strategy Catalog v1 implementation
- Add acceptance criteria documentation (AC-0013 through AC-0023) - Add Line-0003 discovery documentation - Implement Strategy Catalog v1 with new database migration - Update portfolio API endpoints and schemas - Refactor strategy and portfolio services - Add strategy management CLI commands - Update domain models with strategy catalog support - Add comprehensive tests for v1 strategies - Update design documentation (data model, interfaces, runtime flows)
This commit is contained in:
@@ -17,7 +17,10 @@ class StrategySetResponse(BaseModel):
|
||||
name: str
|
||||
config: str
|
||||
nick: str
|
||||
status: Literal["active", "inactive"] = "active"
|
||||
revision: int = 1
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
class LedgerEntryResponse(BaseModel):
|
||||
@@ -55,6 +58,9 @@ class PortfolioSummary(BaseModel):
|
||||
price: float = 0.0
|
||||
profit_buffer: float = 0.0
|
||||
active_strategy_id: str | None = None
|
||||
strategy_name: str | None = None
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
strategy_revision: int | None = Field(default=None, ge=1)
|
||||
avabile_strategies: list[str] | None = None
|
||||
managed_by_daemon_id: str | None = None
|
||||
created_at: datetime | None = None
|
||||
@@ -74,12 +80,11 @@ class PortfolioDetailResponse(BaseModel):
|
||||
|
||||
|
||||
class CreatePortfolioRequest(PortfolioBase):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str # required, overrides Optional from PortfolioBase; used as (owner_user_id, name) identifier
|
||||
asset_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
|
||||
strategy_id: str = Field(..., min_length=1)
|
||||
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)
|
||||
@@ -96,13 +101,12 @@ class CreatePortfolioRequest(PortfolioBase):
|
||||
|
||||
|
||||
class UpdatePortfolioRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
broker: str | None = None
|
||||
status: str | None = None
|
||||
name: 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
|
||||
strategy_id: str | None = Field(default=None, min_length=1)
|
||||
asset_price: float | None = Field(default=None, ge=0.0)
|
||||
managed_by_daemon_id: str | None = None
|
||||
|
||||
@@ -179,11 +183,17 @@ class ErrorResponse(BaseModel):
|
||||
|
||||
|
||||
class CreateStrategySetRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str
|
||||
config: str
|
||||
status: Literal["active", "inactive"] = "active"
|
||||
|
||||
|
||||
class UpdateStrategySetRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str | None = None
|
||||
config: str | None = None
|
||||
status: Literal["active", "inactive"] | None = None
|
||||
|
||||
|
||||
+46
-4
@@ -233,6 +233,8 @@ class PortfolioReturnSummaryResponse(BaseModel):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class PortfolioCreateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
asset_id: str = Field(..., min_length=1)
|
||||
broker: str = Field(..., min_length=1)
|
||||
name: str | None = None
|
||||
@@ -240,18 +242,18 @@ class PortfolioCreateRequest(BaseModel):
|
||||
asset_quantity: float = Field(default=0.0, ge=0.0)
|
||||
asset_price: float = Field(default=0.0, ge=0.0)
|
||||
profit_buffer: float = Field(default=0.0, ge=0.0)
|
||||
strategy_name: str | None = None
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
strategy_id: str = Field(..., min_length=1)
|
||||
managed_by_daemon_id: str | None = None
|
||||
user_id: str | None = None # admin-only: override owner
|
||||
|
||||
|
||||
class PortfolioUpdateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
broker: str | None = None
|
||||
status: str | None = None
|
||||
name: str | None = None
|
||||
strategy_name: str | None = None
|
||||
strategy_params: dict[str, Any] | None = None
|
||||
strategy_id: str | None = Field(default=None, min_length=1)
|
||||
asset_price: float | None = Field(default=None, ge=0.0)
|
||||
managed_by_daemon_id: str | None = None
|
||||
|
||||
@@ -285,6 +287,7 @@ class PortfolioItem(BaseModel):
|
||||
active_strategy_id: str | None = None
|
||||
strategy_name: str | None = None
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
strategy_revision: int | None = Field(default=None, ge=1)
|
||||
markets: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
@@ -293,6 +296,45 @@ class PortfolioListResponse(BaseModel):
|
||||
total: int
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /v1/strategies, CRUD /v1/admin/strategies
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
StrategyStatus = Literal["active", "inactive"]
|
||||
|
||||
|
||||
class StrategyResponse(BaseModel):
|
||||
strategy_id: str
|
||||
name: str
|
||||
config: dict[str, Any] = Field(default_factory=dict)
|
||||
status: StrategyStatus
|
||||
revision: int = Field(ge=1)
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
class StrategyCreateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(..., min_length=1)
|
||||
config: dict[str, Any] = Field(default_factory=dict)
|
||||
status: StrategyStatus = "active"
|
||||
|
||||
|
||||
class StrategyUpdateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str | None = Field(default=None, min_length=1)
|
||||
config: dict[str, Any] | None = None
|
||||
status: StrategyStatus | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_change(self) -> "StrategyUpdateRequest":
|
||||
if not self.model_fields_set:
|
||||
raise ValueError("at least one field is required")
|
||||
return self
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /v1/portfolios/{id}/tags, CRUD /v1/portfolios/{id}/comments (REQ-030, REQ-031)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user