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:
@@ -56,7 +56,7 @@
|
||||
|
||||
### Compatibility Rules
|
||||
|
||||
- Route prefixes /health, /auth, /users, /strategy-sets, and /portfolios define the current internal API surface.
|
||||
- Route prefixes `/health`, `/auth`, `/users`, and `/portfolios` define the unversioned internal API surface. The legacy `/strategy-sets` routes are removed; strategy catalog consumers use `/v1/strategies` and admins use `/v1/admin/strategies`.
|
||||
- Permission semantics read, trade-write, and admin must stay aligned across auth guards and token metadata.
|
||||
- Breaking route or schema changes require REQ and Batch documentation updates before release.
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
- Environment configuration :: app/core/config.py resolves PORTFOLIOHUB_* and TIMESCALEDB_* values before command execution
|
||||
- Portfolio identification (bat-006) :: all portfolio mutation commands accept `<name>` (positional) + `--user <username>` instead of raw portfolio_id; `comment` sub-commands accept portfolio_id (UUID) directly
|
||||
- Tag commands (bat-005) :: `pfhub tag create/list/deactivate/apply/remove` operate on the tags master table; `pfhub portfolio tag add/list/remove` has been removed (breaking change)
|
||||
- Portfolio strategy selection :: `pfhub portfolio create` requires `--strategy-id`; `portfolio update` accepts optional `--strategy-id`. Strategy names/configs are not accepted on portfolio mutations.
|
||||
- Strategy catalog commands :: `pfhub strategy list/create/update/delete`; list and mutation output includes `status` and `revision`, update preserves the strategy ID, and referenced strategies cannot be deleted.
|
||||
|
||||
### Outputs
|
||||
|
||||
@@ -299,18 +301,29 @@
|
||||
### Endpoint: `POST /v1/portfolios`
|
||||
|
||||
- Auth: any valid token; service/client → HTTP 403
|
||||
- Body: `{"asset_id": "<uuid>", "broker": "<str>", "name": "<str|null>", "cash_balance": <float>, "asset_quantity": <float>, "asset_price": <float>, "profit_buffer": <float>, "strategy_name": "<str|null>", "strategy_params": {<dict>}, "managed_by_daemon_id": "<str|null>", "user_id": "<str|null> (admin only)"}`
|
||||
- Response: HTTP 201 `PortfolioItem {"portfolio_id": "<uuid>", "asset_id": "<uuid>", "symbol": "<assets.symbol|null>", "owner_user_id": "<str|null>", "broker": "<str>", "status": "<str>", "name": "<str>", ...}`
|
||||
- Body: `{"asset_id": "<uuid>", "broker": "<str>", "name": "<str|null>", "cash_balance": <float>, "asset_quantity": <float>, "asset_price": <float>, "profit_buffer": <float>, "strategy_id": "<required strategy ID>", "managed_by_daemon_id": "<str|null>", "user_id": "<str|null> (admin only)"}`
|
||||
- Response: HTTP 201 `PortfolioItem {"portfolio_id": "<uuid>", ..., "active_strategy_id": "<str>", "strategy_name": "<str>", "strategy_params": {<dict>}, "strategy_revision": <int>, ...}`
|
||||
- Note: `symbol` is populated from the server-side joined asset row (`portfolios.asset_id -> assets.asset_id -> assets.symbol`), not from client-side catalog reconstruction.
|
||||
- Error: HTTP 422 if asset not found or inactive; HTTP 403 if service/client token
|
||||
- Error: HTTP 403 if service/client token; HTTP 404 if `strategy_id` is unknown; HTTP 409 if the strategy is inactive; HTTP 422 for a missing/invalid `strategy_id` or an unknown/inactive asset
|
||||
|
||||
### Endpoint: `PATCH /v1/portfolios/{portfolio_id}`
|
||||
|
||||
- Auth: any valid token; service/client → HTTP 403; ownership required
|
||||
- Body: `{"broker": "<str|null>", "status": "<str|null>", "name": "<str|null>", "strategy_name": "<str|null>", "strategy_params": {<dict|null>}, "asset_price": <float|null>, "managed_by_daemon_id": "<str|null>"}`
|
||||
- Response: HTTP 200 `PortfolioItem {"portfolio_id": "<uuid>", "asset_id": "<uuid>", "symbol": "<assets.symbol|null>", ...}`
|
||||
- Body: `{"broker": "<str|null>", "status": "<str|null>", "name": "<str|null>", "strategy_id": "<str|null>", "asset_price": <float|null>, "managed_by_daemon_id": "<str|null>"}`
|
||||
- Response: HTTP 200 `PortfolioItem {"portfolio_id": "<uuid>", ..., "active_strategy_id": "<str>", "strategy_name": "<str>", "strategy_params": {<dict>}, "strategy_revision": <int>, ...}`
|
||||
- Note: response `symbol` is emitted from the joined asset row referenced by `asset_id`.
|
||||
- Error: HTTP 403 if service/client or ownership mismatch; HTTP 404 if portfolio not found; HTTP 409 if portfolio is disabled and non-status fields are provided
|
||||
- Error: HTTP 403 if service/client or ownership mismatch; HTTP 404 if portfolio or strategy is not found; HTTP 409 if the portfolio is disabled for the mutation or the selected strategy is inactive; HTTP 422 for an invalid request body
|
||||
|
||||
## Interface: `v1 Strategy Catalog API`
|
||||
|
||||
- `GET /v1/strategies` and `GET /v1/strategies/{strategy_id}` require read permission and expose active strategies only.
|
||||
- `GET /v1/admin/strategies[/{strategy_id}]` and admin `POST`, `PATCH`, `DELETE` require admin permission and expose both active and inactive entries.
|
||||
- Create body: `{"name": "<str>", "config": {<dict>}, "status": "active|inactive"}`; response is `StrategyResponse {strategy_id, name, config, status, revision, created_at, updated_at}`.
|
||||
- Patch accepts at least one of `name`, `config`, or `status`. An effective no-op preserves `revision`; a real mutation increments it while preserving `strategy_id` and keeping the derived `nick` consistent with name/config.
|
||||
- Strategy PATCH locks the catalog row (`FOR UPDATE`) through the mutation transaction. Concurrent partial PATCH requests therefore serialize and derive `nick` from the latest committed name/config pair.
|
||||
- Inactive strategies remain readable to admins and remain attached to existing portfolios, but cannot be selected for new or updated portfolio assignments.
|
||||
- New portfolio selection takes a `FOR SHARE` lock on the active strategy in the same transaction as the portfolio INSERT/UPDATE. PATCH/DELETE take `FOR UPDATE`, preventing concurrent inactivation/deletion from racing the selection; DELETE also checks both reference forms and deletes in that transaction.
|
||||
- Delete returns HTTP 409 while any portfolio references the strategy. Unknown IDs return HTTP 404; schema validation returns HTTP 422; conflicts return HTTP 409.
|
||||
|
||||
### Endpoint: `DELETE /v1/portfolios/{portfolio_id}` (Soft Delete)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user