"""Portfolio domain models.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import date, datetime from decimal import Decimal from enum import Enum from typing import Any, Literal, Protocol, runtime_checkable class MarketCode(str, Enum): KRX = "KRX" NXT = "NXT" SOR = "SOR" NASDAQ = "NASDAQ" NYSE = "NYSE" UPBIT = "UPBIT" BITHUMB = "BITHUMB" @dataclass class Tag: tag_id: str name: str created_by_user_id: str is_admin_tag: bool = False is_active: bool = True created_at: datetime | None = None @dataclass class Asset: asset_id: str symbol: str markets: list[str] display_name: str | None = None status: str = "active" tag_ids: list[str] = field(default_factory=list) created_at: datetime | None = None updated_at: datetime | None = None @dataclass class StrategySet: id: str name: str config: str nick: str status: str = "active" revision: int = 1 created_at: datetime | None = None updated_at: datetime | None = None @dataclass class User: id: str username: str email: str | None = None permission: str = "read" is_active: bool = True password_hash: str | None = None created_at: datetime | None = None updated_at: datetime | None = None last_login_at: datetime | None = None @dataclass class Daemon: id: str owner_user_id: str daemon_slug: str display_name: str | None = None is_active: bool = True created_at: datetime | None = None updated_at: datetime | None = None @dataclass class AuthToken: id: str user_id: str name: str token_prefix: str permission: str token_type: str expires_at: datetime | None = None last_used_at: datetime | None = None revoked_at: datetime | None = None created_at: datetime | None = None runtime_id: str | None = None daemon_id: str | None = None @dataclass class TokenPrincipal: token_id: str user_id: str permission: str token_type: str runtime_id: str | None = None daemon_id: str | None = None @dataclass class Portfolio: portfolio_id: str broker: str status: str name: str symbol: str asset_id: str | None = None display_name: str | None = None tags: list[str] | None = None tag_ids: list[str] | None = None owner_user_id: str | None = None cash_balance: float = 0.0 asset_quantity: float = 0.0 price: float = 0.0 profit_buffer: float = 0.0 active_strategy_id: str | None = None strategy_name: str | None = None # REQ-055: strategy_sets.name JOIN 결과 strategy_nick: str | None = None # REQ-055: strategy_sets.nick JOIN 결과 strategy_params: dict[str, Any] = field(default_factory=dict) # REQ-055: strategy_sets.config 파싱 결과 strategy_revision: int | None = None markets: list[str] = field(default_factory=list) # REQ-055: assets.markets JOIN 결과 avabile_strategies: list[str] | None = None created_at: datetime | None = None updated_at: datetime | None = None last_trade_at: datetime | None = None managed_by_daemon_id: str | None = None @dataclass class CorporateAction: corporate_action_id: str portfolio_id: str asset_id: str | None action_type: str effective_date: date quantity_before: Decimal quantity_after: Decimal average_price_before: Decimal average_price_after: Decimal total_cost_basis: Decimal cash_balance: Decimal profit_buffer: Decimal evidence_ref: str idempotency_key: str payload_hash: str ledger_entry_id: int actor_user_id: str | None = None recorded_at: datetime | None = None idempotent_replay: bool = False @dataclass class LedgerEntry: portfolio_id: str quantity: float price: float price_decimal: Decimal | None = None ledger_entry_id: int = 0 order_id: str | None = None execution_id: str | None = None side: str | None = None fee_amount: float = 0.0 tax_amount: float = 0.0 tax_amount_decimal: Decimal | None = None executed_at: datetime | None = None recorded_at: datetime | None = None cash_balance_after: float = 0.0 asset_quantity_after: float = 0.0 total_value_after: float = 0.0 profit_buffer_after: float = 0.0 strategy_set_id: str = "legacy" note: str = "" action: str | None = None source: str = "broker" target_ledger_entry_id: int | None = None confirmation_evidence: str | None = None actor_user_id: str | None = None execution_venue: Literal["KRX", "NXT"] | None = None order_venue: Literal["KRX", "NXT", "SOR"] | None = None corporate_action: dict[str, Any] | None = None def __post_init__(self) -> None: if self.side is None and self.action: self.side = str(self.action).lower() if self.action is None and self.side: self.action = str(self.side).upper() if self.executed_at is None: self.executed_at = self.recorded_at @dataclass class PortfolioComment: id: str portfolio_id: str content: str created_at: datetime | None = None updated_at: datetime | None = None @dataclass class PortfolioTag: portfolio_id: str tag: str created_at: datetime | None = None @runtime_checkable class LiveAssetSource(Protocol): def get_asset_quantity(self, symbol: str) -> float: ... @dataclass class BrokerCredential: user_id: str broker: str account_ref: str encrypted_payload: str credential_version: int = 1 last_rotated_at: datetime | None = None daemon_id: str | None = None