feat(portfolio): add asset name field across API, DB, and CLI
This commit is contained in:
@@ -79,6 +79,7 @@ def portfolio_status(
|
|||||||
def create_portfolio(
|
def create_portfolio(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
portfolio_id: str = typer.Argument(..., help="Portfolio identifier."),
|
portfolio_id: str = typer.Argument(..., help="Portfolio identifier."),
|
||||||
|
name: str | None = typer.Option(None, help="Asset display name."),
|
||||||
symbol: str = typer.Option(..., help="Trading symbol."),
|
symbol: str = typer.Option(..., help="Trading symbol."),
|
||||||
broker: str = typer.Option("kis", help="Broker code."),
|
broker: str = typer.Option("kis", help="Broker code."),
|
||||||
status: str = typer.Option("enabled", help="Portfolio status."),
|
status: str = typer.Option("enabled", help="Portfolio status."),
|
||||||
@@ -96,6 +97,7 @@ def create_portfolio(
|
|||||||
lambda service, app_context: service.create_portfolio(
|
lambda service, app_context: service.create_portfolio(
|
||||||
CreatePortfolioRequest(
|
CreatePortfolioRequest(
|
||||||
portfolio_id=portfolio_id,
|
portfolio_id=portfolio_id,
|
||||||
|
name=name,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
broker=broker,
|
broker=broker,
|
||||||
status=status,
|
status=status,
|
||||||
@@ -117,6 +119,7 @@ def update_portfolio(
|
|||||||
portfolio_id: str = typer.Argument(..., help="Portfolio identifier."),
|
portfolio_id: str = typer.Argument(..., help="Portfolio identifier."),
|
||||||
broker: str | None = typer.Option(None, help="Broker code."),
|
broker: str | None = typer.Option(None, help="Broker code."),
|
||||||
status: str | None = typer.Option(None, help="Portfolio status."),
|
status: str | None = typer.Option(None, help="Portfolio status."),
|
||||||
|
name: str | None = typer.Option(None, help="Asset display name."),
|
||||||
symbol: str | None = typer.Option(None, help="Trading symbol."),
|
symbol: str | None = typer.Option(None, help="Trading symbol."),
|
||||||
strategy_name: str | None = typer.Option(None, help="Strategy name."),
|
strategy_name: str | None = typer.Option(None, help="Strategy name."),
|
||||||
strategy_params: str | None = typer.Option(None, help="Strategy params as JSON object."),
|
strategy_params: str | None = typer.Option(None, help="Strategy params as JSON object."),
|
||||||
@@ -132,6 +135,7 @@ def update_portfolio(
|
|||||||
UpdatePortfolioRequest(
|
UpdatePortfolioRequest(
|
||||||
broker=broker,
|
broker=broker,
|
||||||
status=status,
|
status=status,
|
||||||
|
name=name,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
strategy_name=strategy_name,
|
strategy_name=strategy_name,
|
||||||
strategy_params=parsed_strategy_params,
|
strategy_params=parsed_strategy_params,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class Portfolio:
|
|||||||
portfolio_id: str
|
portfolio_id: str
|
||||||
broker: str
|
broker: str
|
||||||
status: str
|
status: str
|
||||||
|
name: str
|
||||||
symbol: str
|
symbol: str
|
||||||
cash_balance: float = 0.0
|
cash_balance: float = 0.0
|
||||||
asset_quantity: float = 0.0
|
asset_quantity: float = 0.0
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class PortfolioDomainRepository(Protocol):
|
|||||||
portfolio_id: str,
|
portfolio_id: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
*,
|
*,
|
||||||
|
name: str | None = None,
|
||||||
broker: str = "kis",
|
broker: str = "kis",
|
||||||
status: str = "enabled",
|
status: str = "enabled",
|
||||||
active_strategy_id: str | None = None,
|
active_strategy_id: str | None = None,
|
||||||
@@ -34,6 +35,7 @@ class PortfolioDomainRepository(Protocol):
|
|||||||
*,
|
*,
|
||||||
broker: str,
|
broker: str,
|
||||||
status: str,
|
status: str,
|
||||||
|
name: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
cash_balance: float,
|
cash_balance: float,
|
||||||
asset_quantity: float,
|
asset_quantity: float,
|
||||||
@@ -99,6 +101,7 @@ def apply_buy(
|
|||||||
portfolio_id=portfolio.portfolio_id,
|
portfolio_id=portfolio.portfolio_id,
|
||||||
broker=portfolio.broker,
|
broker=portfolio.broker,
|
||||||
status=portfolio.status,
|
status=portfolio.status,
|
||||||
|
name=portfolio.name,
|
||||||
symbol=portfolio.symbol,
|
symbol=portfolio.symbol,
|
||||||
cash_balance=float(portfolio.cash_balance) - total_cost,
|
cash_balance=float(portfolio.cash_balance) - total_cost,
|
||||||
asset_quantity=float(portfolio.asset_quantity) + quantity,
|
asset_quantity=float(portfolio.asset_quantity) + quantity,
|
||||||
@@ -159,6 +162,7 @@ def apply_sell(
|
|||||||
portfolio_id=portfolio.portfolio_id,
|
portfolio_id=portfolio.portfolio_id,
|
||||||
broker=portfolio.broker,
|
broker=portfolio.broker,
|
||||||
status=portfolio.status,
|
status=portfolio.status,
|
||||||
|
name=portfolio.name,
|
||||||
symbol=portfolio.symbol,
|
symbol=portfolio.symbol,
|
||||||
cash_balance=float(portfolio.cash_balance) + net_proceeds,
|
cash_balance=float(portfolio.cash_balance) + net_proceeds,
|
||||||
asset_quantity=float(portfolio.asset_quantity) - quantity,
|
asset_quantity=float(portfolio.asset_quantity) - quantity,
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class PortfolioRepository(Protocol):
|
|||||||
portfolio_id: str,
|
portfolio_id: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
*,
|
*,
|
||||||
|
name: str | None = None,
|
||||||
broker: str = "kis",
|
broker: str = "kis",
|
||||||
status: str = "enabled",
|
status: str = "enabled",
|
||||||
active_strategy_id: str | None = None,
|
active_strategy_id: str | None = None,
|
||||||
@@ -99,6 +100,7 @@ class PortfolioRepository(Protocol):
|
|||||||
*,
|
*,
|
||||||
broker: str,
|
broker: str,
|
||||||
status: str,
|
status: str,
|
||||||
|
name: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
cash_balance: float,
|
cash_balance: float,
|
||||||
asset_quantity: float,
|
asset_quantity: float,
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ _SELECT_PORTFOLIO = """
|
|||||||
portfolio_id,
|
portfolio_id,
|
||||||
broker,
|
broker,
|
||||||
status,
|
status,
|
||||||
|
name,
|
||||||
symbol,
|
symbol,
|
||||||
cash_balance,
|
cash_balance,
|
||||||
asset_quantity,
|
asset_quantity,
|
||||||
@@ -214,6 +215,7 @@ class PostgresPortfolioRepository:
|
|||||||
portfolio_id TEXT PRIMARY KEY,
|
portfolio_id TEXT PRIMARY KEY,
|
||||||
broker TEXT NOT NULL DEFAULT 'kis',
|
broker TEXT NOT NULL DEFAULT 'kis',
|
||||||
status TEXT NOT NULL DEFAULT 'enabled',
|
status TEXT NOT NULL DEFAULT 'enabled',
|
||||||
|
name TEXT NOT NULL,
|
||||||
symbol TEXT NOT NULL,
|
symbol TEXT NOT NULL,
|
||||||
cash_balance DOUBLE PRECISION NOT NULL,
|
cash_balance DOUBLE PRECISION NOT NULL,
|
||||||
asset_quantity DOUBLE PRECISION NOT NULL,
|
asset_quantity DOUBLE PRECISION NOT NULL,
|
||||||
@@ -227,6 +229,9 @@ class PostgresPortfolioRepository:
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
cur.execute("ALTER TABLE portfolios ADD COLUMN IF NOT EXISTS name 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(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS portfolio_ledger (
|
CREATE TABLE IF NOT EXISTS portfolio_ledger (
|
||||||
@@ -321,7 +326,7 @@ class PostgresPortfolioRepository:
|
|||||||
if row is None:
|
if row is None:
|
||||||
raise ValueError("포트폴리오를 찾을 수 없습니다")
|
raise ValueError("포트폴리오를 찾을 수 없습니다")
|
||||||
|
|
||||||
raw_available = row[9] if len(row) > 9 else None
|
raw_available = row[10] if len(row) > 10 else None
|
||||||
if raw_available is None:
|
if raw_available is None:
|
||||||
avabile_strategies = None
|
avabile_strategies = None
|
||||||
elif isinstance(raw_available, list):
|
elif isinstance(raw_available, list):
|
||||||
@@ -333,16 +338,17 @@ class PostgresPortfolioRepository:
|
|||||||
portfolio_id=row[0],
|
portfolio_id=row[0],
|
||||||
broker=row[1] or "kis",
|
broker=row[1] or "kis",
|
||||||
status=row[2] or "enabled",
|
status=row[2] or "enabled",
|
||||||
symbol=row[3],
|
name=row[3],
|
||||||
cash_balance=float(row[4]),
|
symbol=row[4],
|
||||||
asset_quantity=float(row[5]),
|
cash_balance=float(row[5]),
|
||||||
price=float(row[6]),
|
asset_quantity=float(row[6]),
|
||||||
profit_buffer=float(row[7]),
|
price=float(row[7]),
|
||||||
active_strategy_id=row[8] if len(row) > 8 else None,
|
profit_buffer=float(row[8]),
|
||||||
|
active_strategy_id=row[9] if len(row) > 9 else None,
|
||||||
avabile_strategies=avabile_strategies,
|
avabile_strategies=avabile_strategies,
|
||||||
created_at=row[10] if len(row) > 10 else None,
|
created_at=row[11] if len(row) > 11 else None,
|
||||||
updated_at=row[11] if len(row) > 11 else None,
|
updated_at=row[12] if len(row) > 12 else None,
|
||||||
last_trade_at=row[12] if len(row) > 12 else None,
|
last_trade_at=row[13] if len(row) > 13 else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -698,6 +704,7 @@ class PostgresPortfolioRepository:
|
|||||||
portfolio_id: str,
|
portfolio_id: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
*,
|
*,
|
||||||
|
name: str | None = None,
|
||||||
broker: str = "kis",
|
broker: str = "kis",
|
||||||
status: str = "enabled",
|
status: str = "enabled",
|
||||||
active_strategy_id: str | None = None,
|
active_strategy_id: str | None = None,
|
||||||
@@ -709,17 +716,18 @@ class PostgresPortfolioRepository:
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO portfolios (
|
INSERT INTO portfolios (
|
||||||
portfolio_id, broker, status, symbol,
|
portfolio_id, broker, status, name, symbol,
|
||||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||||
active_strategy_id, avabile_strategies
|
active_strategy_id, avabile_strategies
|
||||||
)
|
)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
ON CONFLICT (portfolio_id) DO NOTHING
|
ON CONFLICT (portfolio_id) DO NOTHING
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
portfolio_id,
|
portfolio_id,
|
||||||
broker,
|
broker,
|
||||||
status,
|
status,
|
||||||
|
name or symbol,
|
||||||
symbol,
|
symbol,
|
||||||
float(initial_cash),
|
float(initial_cash),
|
||||||
0.0,
|
0.0,
|
||||||
@@ -739,6 +747,7 @@ class PostgresPortfolioRepository:
|
|||||||
*,
|
*,
|
||||||
broker: str,
|
broker: str,
|
||||||
status: str,
|
status: str,
|
||||||
|
name: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
cash_balance: float,
|
cash_balance: float,
|
||||||
asset_quantity: float,
|
asset_quantity: float,
|
||||||
@@ -755,12 +764,12 @@ class PostgresPortfolioRepository:
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO portfolios (
|
INSERT INTO portfolios (
|
||||||
portfolio_id, broker, status, symbol,
|
portfolio_id, broker, status, name, symbol,
|
||||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
cash_balance, asset_quantity, asset_price, profit_buffer,
|
||||||
active_strategy_id, avabile_strategies
|
active_strategy_id, avabile_strategies
|
||||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
RETURNING
|
RETURNING
|
||||||
portfolio_id, broker, status, symbol,
|
portfolio_id, broker, status, name, symbol,
|
||||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
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, created_at, updated_at, last_trade_at
|
||||||
""",
|
""",
|
||||||
@@ -768,6 +777,7 @@ class PostgresPortfolioRepository:
|
|||||||
portfolio_id,
|
portfolio_id,
|
||||||
broker,
|
broker,
|
||||||
status,
|
status,
|
||||||
|
name,
|
||||||
symbol,
|
symbol,
|
||||||
float(cash_balance),
|
float(cash_balance),
|
||||||
float(asset_quantity),
|
float(asset_quantity),
|
||||||
@@ -785,6 +795,7 @@ class PostgresPortfolioRepository:
|
|||||||
allowed = {
|
allowed = {
|
||||||
"broker": "broker",
|
"broker": "broker",
|
||||||
"status": "status",
|
"status": "status",
|
||||||
|
"name": "name",
|
||||||
"symbol": "symbol",
|
"symbol": "symbol",
|
||||||
"asset_price": "asset_price",
|
"asset_price": "asset_price",
|
||||||
"active_strategy_id": "active_strategy_id",
|
"active_strategy_id": "active_strategy_id",
|
||||||
@@ -812,7 +823,7 @@ class PostgresPortfolioRepository:
|
|||||||
SET {', '.join(assignments)}
|
SET {', '.join(assignments)}
|
||||||
WHERE portfolio_id = %s
|
WHERE portfolio_id = %s
|
||||||
RETURNING
|
RETURNING
|
||||||
portfolio_id, broker, status, symbol,
|
portfolio_id, broker, status, name, symbol,
|
||||||
cash_balance, asset_quantity, asset_price, profit_buffer,
|
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, created_at, updated_at, last_trade_at
|
||||||
""",
|
""",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class LedgerEntryResponse(BaseModel):
|
|||||||
class PortfolioBase(BaseModel):
|
class PortfolioBase(BaseModel):
|
||||||
broker: str = "kis"
|
broker: str = "kis"
|
||||||
status: str = "enabled"
|
status: str = "enabled"
|
||||||
|
name: str | None = None
|
||||||
symbol: str
|
symbol: str
|
||||||
|
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ class PortfolioSummary(BaseModel):
|
|||||||
portfolio_id: str
|
portfolio_id: str
|
||||||
broker: str
|
broker: str
|
||||||
status: str
|
status: str
|
||||||
|
name: str
|
||||||
symbol: str
|
symbol: str
|
||||||
cash_balance: float = 0.0
|
cash_balance: float = 0.0
|
||||||
asset_quantity: float = 0.0
|
asset_quantity: float = 0.0
|
||||||
@@ -80,6 +82,7 @@ class CreatePortfolioRequest(PortfolioBase):
|
|||||||
class UpdatePortfolioRequest(BaseModel):
|
class UpdatePortfolioRequest(BaseModel):
|
||||||
broker: str | None = None
|
broker: str | None = None
|
||||||
status: str | None = None
|
status: str | None = None
|
||||||
|
name: str | None = None
|
||||||
symbol: str | None = None
|
symbol: str | None = None
|
||||||
strategy_name: str | None = None
|
strategy_name: str | None = None
|
||||||
strategy_params: dict[str, Any] | None = None
|
strategy_params: dict[str, Any] | None = None
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ def _to_portfolio_summary(portfolio: Portfolio):
|
|||||||
portfolio_id=portfolio.portfolio_id,
|
portfolio_id=portfolio.portfolio_id,
|
||||||
broker=portfolio.broker,
|
broker=portfolio.broker,
|
||||||
status=portfolio.status,
|
status=portfolio.status,
|
||||||
|
name=portfolio.name,
|
||||||
symbol=portfolio.symbol,
|
symbol=portfolio.symbol,
|
||||||
cash_balance=portfolio.cash_balance,
|
cash_balance=portfolio.cash_balance,
|
||||||
asset_quantity=portfolio.asset_quantity,
|
asset_quantity=portfolio.asset_quantity,
|
||||||
@@ -150,6 +151,7 @@ class PortfolioService:
|
|||||||
payload.portfolio_id,
|
payload.portfolio_id,
|
||||||
broker=payload.broker,
|
broker=payload.broker,
|
||||||
status=payload.status,
|
status=payload.status,
|
||||||
|
name=payload.name or payload.symbol,
|
||||||
symbol=payload.symbol,
|
symbol=payload.symbol,
|
||||||
cash_balance=payload.cash_balance,
|
cash_balance=payload.cash_balance,
|
||||||
asset_quantity=payload.asset_quantity,
|
asset_quantity=payload.asset_quantity,
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ Response 200:
|
|||||||
"portfolio_id": "krw-btc",
|
"portfolio_id": "krw-btc",
|
||||||
"broker": "upbit",
|
"broker": "upbit",
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
|
"name": "Bitcoin",
|
||||||
"symbol": "KRW-BTC",
|
"symbol": "KRW-BTC",
|
||||||
"cash_balance": 1000000.0,
|
"cash_balance": 1000000.0,
|
||||||
"asset_quantity": 0.1,
|
"asset_quantity": 0.1,
|
||||||
@@ -82,6 +83,7 @@ Response 200:
|
|||||||
"portfolio_id": "krw-btc",
|
"portfolio_id": "krw-btc",
|
||||||
"broker": "upbit",
|
"broker": "upbit",
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
|
"name": "Bitcoin",
|
||||||
"symbol": "KRW-BTC",
|
"symbol": "KRW-BTC",
|
||||||
"cash_balance": 1000000.0,
|
"cash_balance": 1000000.0,
|
||||||
"asset_quantity": 0.1,
|
"asset_quantity": 0.1,
|
||||||
@@ -133,6 +135,7 @@ curl -X POST "http://localhost:8000/portfolios" \
|
|||||||
"portfolio_id": "krw-btc",
|
"portfolio_id": "krw-btc",
|
||||||
"broker": "upbit",
|
"broker": "upbit",
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
|
"name": "Bitcoin",
|
||||||
"symbol": "KRW-BTC",
|
"symbol": "KRW-BTC",
|
||||||
"strategy_name": "target_value",
|
"strategy_name": "target_value",
|
||||||
"strategy_params": {},
|
"strategy_params": {},
|
||||||
@@ -159,6 +162,7 @@ curl -X PATCH "http://localhost:8000/portfolios/krw-btc" \
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"broker": "upbit",
|
"broker": "upbit",
|
||||||
|
"name": "Bitcoin",
|
||||||
"symbol": "KRW-BTC"
|
"symbol": "KRW-BTC"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
This document defines the database schema in an AI-agent-friendly format.
|
This document defines the database schema in an AI-agent-friendly format.
|
||||||
|
|
||||||
- Source of truth: Alembic revisions in `migrations/versions`
|
- Source of truth: Alembic revisions in `migrations/versions`
|
||||||
- Current baseline revision: `20260403_0007`
|
- Current baseline revision: `20260403_0008`
|
||||||
- Baseline files:
|
- Baseline files:
|
||||||
- `migrations/versions/20260403_0001_initial_portfolio_schema.py`
|
- `migrations/versions/20260403_0001_initial_portfolio_schema.py`
|
||||||
- `migrations/versions/20260403_0002_strategy_set_columns.py`
|
- `migrations/versions/20260403_0002_strategy_set_columns.py`
|
||||||
@@ -14,6 +14,7 @@ This document defines the database schema in an AI-agent-friendly format.
|
|||||||
- `migrations/versions/20260403_0005_auth_user_fields_and_tokens.py`
|
- `migrations/versions/20260403_0005_auth_user_fields_and_tokens.py`
|
||||||
- `migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py`
|
- `migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py`
|
||||||
- `migrations/versions/20260403_0007_bot_principal_permission_only.py`
|
- `migrations/versions/20260403_0007_bot_principal_permission_only.py`
|
||||||
|
- `migrations/versions/20260403_0008_portfolio_name_column.py`
|
||||||
|
|
||||||
## 2. Scope
|
## 2. Scope
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ Purpose:
|
|||||||
|
|
||||||
Current baseline note:
|
Current baseline note:
|
||||||
|
|
||||||
- The columns below describe the currently migrated schema through revision `20260403_0005`.
|
- The columns below describe the currently migrated schema through revision `20260403_0008`.
|
||||||
|
|
||||||
Columns:
|
Columns:
|
||||||
|
|
||||||
@@ -154,6 +155,7 @@ Columns:
|
|||||||
| portfolio_id | Text | NO | - | Primary Key |
|
| portfolio_id | Text | NO | - | Primary Key |
|
||||||
| broker | Text | NO | "kis" | Broker code |
|
| broker | Text | NO | "kis" | Broker code |
|
||||||
| status | Text | NO | "enabled" | Portfolio status |
|
| status | Text | NO | "enabled" | Portfolio status |
|
||||||
|
| name | Text | NO | - | Asset name |
|
||||||
| symbol | Text | NO | - | Trading symbol |
|
| symbol | Text | NO | - | Trading symbol |
|
||||||
| cash_balance | Float | NO | - | Current cash balance |
|
| cash_balance | Float | NO | - | Current cash balance |
|
||||||
| asset_quantity | Float | NO | - | Current asset quantity |
|
| asset_quantity | Float | NO | - | Current asset quantity |
|
||||||
@@ -294,3 +296,4 @@ When schema changes:
|
|||||||
| 20260403_0005 | migrations/versions/20260403_0005_auth_user_fields_and_tokens.py | Add password/permission/login fields to `users` and create `auth_tokens` table |
|
| 20260403_0005 | migrations/versions/20260403_0005_auth_user_fields_and_tokens.py | Add password/permission/login fields to `users` and create `auth_tokens` table |
|
||||||
| 20260403_0006 | migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py | Add `bot_principals` table and `auth_tokens.bot_principal_id` for bot API key management |
|
| 20260403_0006 | migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py | Add `bot_principals` table and `auth_tokens.bot_principal_id` for bot API key management |
|
||||||
| 20260403_0007 | migrations/versions/20260403_0007_bot_principal_permission_only.py | Remove `bot_principals.owner_user_id` and enforce bot-local `permission` model |
|
| 20260403_0007 | migrations/versions/20260403_0007_bot_principal_permission_only.py | Remove `bot_principals.owner_user_id` and enforce bot-local `permission` model |
|
||||||
|
| 20260403_0008 | migrations/versions/20260403_0008_portfolio_name_column.py | Add non-null `portfolios.name` (asset display name), backfilled from `symbol` |
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ Bootstrap rule:
|
|||||||
- `portfolio_id: str`
|
- `portfolio_id: str`
|
||||||
- `broker: str = "kis"`
|
- `broker: str = "kis"`
|
||||||
- `status: str = "enabled"`
|
- `status: str = "enabled"`
|
||||||
|
- `name: str | null` (when null, defaults to `symbol`)
|
||||||
- `symbol: str`
|
- `symbol: str`
|
||||||
- `strategy_name: str = "target_value"`
|
- `strategy_name: str = "target_value"`
|
||||||
- `strategy_params: object = {}`
|
- `strategy_params: object = {}`
|
||||||
@@ -401,6 +402,7 @@ Bootstrap rule:
|
|||||||
|
|
||||||
- `broker: str | null`
|
- `broker: str | null`
|
||||||
- `status: str | null`
|
- `status: str | null`
|
||||||
|
- `name: str | null`
|
||||||
- `symbol: str | null`
|
- `symbol: str | null`
|
||||||
- `strategy_name: str | null`
|
- `strategy_name: str | null`
|
||||||
- `strategy_params: object | null`
|
- `strategy_params: object | null`
|
||||||
@@ -492,6 +494,7 @@ Bootstrap rule:
|
|||||||
- `portfolio_id: str`
|
- `portfolio_id: str`
|
||||||
- `broker: str`
|
- `broker: str`
|
||||||
- `status: str`
|
- `status: str`
|
||||||
|
- `name: str`
|
||||||
- `symbol: str`
|
- `symbol: str`
|
||||||
- `cash_balance: float`
|
- `cash_balance: float`
|
||||||
- `asset_quantity: float`
|
- `asset_quantity: float`
|
||||||
@@ -553,7 +556,7 @@ Portfolio subcommands:
|
|||||||
|
|
||||||
- `portfoliohub portfolio list [--broker ...] [--status ...]`
|
- `portfoliohub portfolio list [--broker ...] [--status ...]`
|
||||||
- `portfoliohub portfolio status <portfolio_id> [--recent 5]`
|
- `portfoliohub portfolio status <portfolio_id> [--recent 5]`
|
||||||
- `portfoliohub portfolio create <portfolio_id> --symbol ... [options] [--force]`
|
- `portfoliohub portfolio create <portfolio_id> --symbol ... [--name ...] [options] [--force]`
|
||||||
- `portfoliohub portfolio update <portfolio_id> [options] [--force]`
|
- `portfoliohub portfolio update <portfolio_id> [options] [--force]`
|
||||||
- `portfoliohub portfolio buy <portfolio_id> --quantity ... --price ... [options] [--force]`
|
- `portfoliohub portfolio buy <portfolio_id> --quantity ... --price ... [options] [--force]`
|
||||||
- `portfoliohub portfolio sell <portfolio_id> --quantity ... --price ... [options] [--force]`
|
- `portfoliohub portfolio sell <portfolio_id> --quantity ... --price ... [options] [--force]`
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""add portfolio name column
|
||||||
|
|
||||||
|
Revision ID: 20260403_0008
|
||||||
|
Revises: 20260403_0007
|
||||||
|
Create Date: 2026-04-03 14:45:00
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
|
revision = "20260403_0008"
|
||||||
|
down_revision = "20260403_0007"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def _table_exists(inspector, table_name: str) -> bool:
|
||||||
|
return table_name in inspector.get_table_names()
|
||||||
|
|
||||||
|
|
||||||
|
def _column_exists(inspector, table_name: str, column_name: str) -> bool:
|
||||||
|
return any(column["name"] == column_name for column in inspector.get_columns(table_name))
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = inspect(bind)
|
||||||
|
|
||||||
|
if not _table_exists(inspector, "portfolios"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _column_exists(inspector, "portfolios", "name"):
|
||||||
|
op.add_column("portfolios", sa.Column("name", sa.Text(), nullable=True))
|
||||||
|
|
||||||
|
op.execute("UPDATE portfolios SET name = symbol WHERE name IS NULL OR name = ''")
|
||||||
|
op.alter_column("portfolios", "name", existing_type=sa.Text(), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = inspect(bind)
|
||||||
|
|
||||||
|
if not _table_exists(inspector, "portfolios"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if _column_exists(inspector, "portfolios", "name"):
|
||||||
|
op.drop_column("portfolios", "name")
|
||||||
@@ -110,6 +110,7 @@ class FakeRepo:
|
|||||||
portfolio_id: str,
|
portfolio_id: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
*,
|
*,
|
||||||
|
name: str | None = None,
|
||||||
broker: str = "kis",
|
broker: str = "kis",
|
||||||
status: str = "enabled",
|
status: str = "enabled",
|
||||||
active_strategy_id: str | None = None,
|
active_strategy_id: str | None = None,
|
||||||
@@ -121,6 +122,7 @@ class FakeRepo:
|
|||||||
portfolio_id=portfolio_id,
|
portfolio_id=portfolio_id,
|
||||||
broker=broker,
|
broker=broker,
|
||||||
status=status,
|
status=status,
|
||||||
|
name=name or symbol,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
cash_balance=initial_cash,
|
cash_balance=initial_cash,
|
||||||
active_strategy_id=active_strategy_id,
|
active_strategy_id=active_strategy_id,
|
||||||
@@ -134,6 +136,7 @@ class FakeRepo:
|
|||||||
*,
|
*,
|
||||||
broker: str,
|
broker: str,
|
||||||
status: str,
|
status: str,
|
||||||
|
name: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
cash_balance: float,
|
cash_balance: float,
|
||||||
asset_quantity: float,
|
asset_quantity: float,
|
||||||
@@ -146,6 +149,7 @@ class FakeRepo:
|
|||||||
portfolio_id=portfolio_id,
|
portfolio_id=portfolio_id,
|
||||||
broker=broker,
|
broker=broker,
|
||||||
status=status,
|
status=status,
|
||||||
|
name=name,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
cash_balance=cash_balance,
|
cash_balance=cash_balance,
|
||||||
asset_quantity=asset_quantity,
|
asset_quantity=asset_quantity,
|
||||||
@@ -275,6 +279,7 @@ def _portfolio() -> Portfolio:
|
|||||||
portfolio_id="005930:tv",
|
portfolio_id="005930:tv",
|
||||||
broker="kis",
|
broker="kis",
|
||||||
status="enabled",
|
status="enabled",
|
||||||
|
name="Samsung Electronics",
|
||||||
symbol="005930",
|
symbol="005930",
|
||||||
cash_balance=1_000_000.0,
|
cash_balance=1_000_000.0,
|
||||||
asset_quantity=1.0,
|
asset_quantity=1.0,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class FakeRepo:
|
|||||||
portfolio_id: str,
|
portfolio_id: str,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
*,
|
*,
|
||||||
|
name: str | None = None,
|
||||||
broker: str = "kis",
|
broker: str = "kis",
|
||||||
status: str = "enabled",
|
status: str = "enabled",
|
||||||
active_strategy_id: str | None = None,
|
active_strategy_id: str | None = None,
|
||||||
@@ -36,6 +37,7 @@ class FakeRepo:
|
|||||||
portfolio_id=portfolio_id,
|
portfolio_id=portfolio_id,
|
||||||
broker=broker,
|
broker=broker,
|
||||||
status=status,
|
status=status,
|
||||||
|
name=name or symbol,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
cash_balance=initial_cash,
|
cash_balance=initial_cash,
|
||||||
active_strategy_id=active_strategy_id,
|
active_strategy_id=active_strategy_id,
|
||||||
@@ -155,6 +157,7 @@ def _portfolio() -> Portfolio:
|
|||||||
portfolio_id="p1",
|
portfolio_id="p1",
|
||||||
broker="kis",
|
broker="kis",
|
||||||
status="enabled",
|
status="enabled",
|
||||||
|
name="Samsung Electronics",
|
||||||
symbol="005930",
|
symbol="005930",
|
||||||
cash_balance=1_000_000.0,
|
cash_balance=1_000_000.0,
|
||||||
asset_quantity=0.0,
|
asset_quantity=0.0,
|
||||||
|
|||||||
Reference in New Issue
Block a user