10 KiB
10 KiB
PortfolioHub DB Schema Specification
1. Purpose
This document defines the database schema in an AI-agent-friendly format.
- Source of truth: Alembic revisions in
migrations/versions - Current baseline revision:
20260403_0007 - Baseline files:
migrations/versions/20260403_0001_initial_portfolio_schema.pymigrations/versions/20260403_0002_strategy_set_columns.pymigrations/versions/20260403_0003_portfolio_strategy_refs.pymigrations/versions/20260403_0004_users_table.pymigrations/versions/20260403_0005_auth_user_fields_and_tokens.pymigrations/versions/20260403_0006_bot_principals_and_bot_tokens.pymigrations/versions/20260403_0007_bot_principal_permission_only.py
2. Scope
Schema objects covered by baseline revision:
strategy_setsusersauth_tokensbot_principalsportfoliosportfolio_ledger- secondary indexes
- Timescale hypertable conversion rule for
portfolio_ledger
3. Conventions
- Data type names follow SQLAlchemy/Alembic declaration.
NULLmeans nullable.- Default means DB-level
server_defaultwhere defined. - Relationship columns are documented even when no FK constraint exists.
4. Tables
4.1 users
Purpose:
- Stores admin-managed user records for operational ownership and future identity mapping.
Columns:
| Column | Type | Null | Default | Constraints / Notes |
|---|---|---|---|---|
| id | Text | NO | - | Primary Key |
| username | Text | NO | - | Unique username |
| Text | YES | - | Unique when present | |
| permission | Text | NO | "read" | User permission level |
| is_active | Boolean | NO | TRUE | Whether login is allowed |
| password_hash | Text | YES | - | Scrypt password hash |
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
| last_login_at | DateTime(timezone=True) | YES | - | Last successful login timestamp |
Constraints:
- Primary key:
id - Unique:
uq_users_usernameon (username) - Unique:
uq_users_emailon (email)
4.2 auth_tokens
Purpose:
- Stores DB-issued session tokens and client tokens.
Columns:
| Column | Type | Null | Default | Constraints / Notes |
|---|---|---|---|---|
| id | Text | NO | - | Primary Key |
| user_id | Text | NO | - | Logical user reference |
| name | Text | NO | - | Human-readable token name |
| token_prefix | Text | NO | - | Display prefix for issued token |
| token_hash | Text | NO | - | Unique SHA-256 token hash |
| permission | Text | NO | - | Effective token permission |
| token_type | Text | NO | - | session, client, or bot |
| bot_principal_id | Text | YES | - | Logical reference to bot_principals.id |
| expires_at | DateTime(timezone=True) | YES | - | Expiration time |
| last_used_at | DateTime(timezone=True) | YES | - | Last successful use |
| revoked_at | DateTime(timezone=True) | YES | - | Revocation time |
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
Constraints:
- Primary key:
id - Unique:
uq_auth_tokens_token_hashon (token_hash)
4.3 bot_principals
Purpose:
- Stores external bot identities for admin-managed API key issuance and lifecycle control.
Columns:
| Column | Type | Null | Default | Constraints / Notes |
|---|---|---|---|---|
| id | Text | NO | - | Primary Key |
| name | Text | NO | - | Unique bot name |
| permission | Text | NO | - | Bot permission ceiling |
| description | Text | YES | - | Optional operator description |
| is_active | Boolean | NO | TRUE | When false, bot tokens are rejected |
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
| deactivated_at | DateTime(timezone=True) | YES | - | Deactivation time |
Constraints:
- Primary key:
id - Unique:
uq_bot_principals_nameon (name)
4.3 strategy_sets
Purpose:
- Stores normalized strategy configuration snapshots.
Columns:
| Column | Type | Null | Default | Constraints / Notes |
|---|---|---|---|---|
| id | Text | NO | - | Primary Key |
| name | Text | NO | - | Strategy name, part of unique pair |
| config | Text | NO | "" | Strategy config payload, part of unique pair |
| nick | Text | NO | - | Unique strategy set nickname |
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
Constraints:
- Primary key:
id - Unique:
uq_strategy_sets_name_configon (name,config) - Unique:
uq_strategy_sets_nickon (nick)
4.4 portfolios
Purpose:
- Stores current portfolio state and strategy linkage.
Current baseline note:
- The columns below describe the currently migrated schema through revision
20260403_0005.
Columns:
| Column | Type | Null | Default | Constraints / Notes |
|---|---|---|---|---|
| portfolio_id | Text | NO | - | Primary Key |
| broker | Text | NO | "kis" | Broker code |
| status | Text | NO | "enabled" | Portfolio status |
| symbol | Text | NO | - | Trading symbol |
| cash_balance | Float | NO | - | Current cash balance |
| asset_quantity | Float | NO | - | Current asset quantity |
| asset_price | Float | NO | 0 | Current price snapshot |
| profit_buffer | Float | NO | 0 | Profit buffer amount |
| active_strategy_id | Text | YES | - | Logical reference to strategy_sets.id |
| avabile_strategies | Text[] | YES | - | Nullable array of strategy_sets.id values |
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
| last_trade_at | DateTime(timezone=True) | YES | - | Last trade time |
Constraints:
- Primary key:
portfolio_id - Foreign key constraints: none in baseline
Constraint notes:
active_strategy_idis a nullable logical reference tostrategy_sets.idavabile_strategiesstores zero or more selectable strategy set IDs when present
4.5 portfolio_ledger
Purpose:
- Stores append-like ledger snapshots per portfolio action.
Columns:
| Column | Type | Null | Default | Constraints / Notes |
|---|---|---|---|---|
| portfolio_id | Text | NO | - | Logical reference to portfolios.portfolio_id (no FK) |
| recorded_at | DateTime(timezone=True) | NO | - | Event timestamp |
| action | Text | NO | - | Action type |
| price | Float | NO | - | Trade/valuation unit price |
| quantity | Float | NO | - | Quantity delta |
| fee_amount | Float | NO | 0 | Fees |
| tax_amount | Float | NO | 0 | Taxes |
| cash_balance_after | Float | NO | - | Post-action cash |
| asset_quantity_after | Float | NO | - | Post-action asset quantity |
| total_value_after | Float | NO | - | Post-action total value |
| profit_buffer_after | Float | NO | 0 | Post-action profit buffer |
| strategy_set_id | Text | NO | "legacy" | Logical strategy set reference |
| note | Text | NO | "" | Operator/system note |
Constraints:
- Primary key: none in baseline
- Foreign key constraints: none in baseline
5. Indexes
5.1 ix_users_created_at
- Table:
users - Columns/expressions: (
created_at) - Unique: no
- Intent: speed operational listing by recency.
5.2 idx_auth_tokens_user_created
- Table:
auth_tokens - Columns/expressions: (
user_id,created_at) - Unique: no
- Intent: fetch current user's issued tokens efficiently.
5.3 idx_auth_tokens_hash_active
- Table:
auth_tokens - Columns/expressions: (
token_hash) - Unique: no
- Intent: resolve bearer tokens efficiently.
5.4 idx_auth_tokens_bot_created
- Table:
auth_tokens - Columns/expressions: (
bot_principal_id,created_at) - Unique: no
- Intent: fetch bot-issued token lists efficiently.
5.5 idx_bot_principals_active_created
- Table:
bot_principals - Columns/expressions: (
is_active,created_at) - Unique: no
- Intent: speed operational filtering of active/inactive bots.
5.6 idx_portfolio_ledger_portfolio_time
- Table:
portfolio_ledger - Columns/expressions: (
portfolio_id,recorded_at DESC) - Unique: no
- Intent: fetch recent ledger entries per portfolio efficiently.
5.7 idx_portfolios_broker_status_updated
- Table:
portfolios - Columns/expressions: (
broker,status,updated_at DESC) - Unique: no
- Intent: speed list/filter queries by broker/status and recency.
6. TimescaleDB Behavior
Conditional behavior in migration upgrade:
- Checks whether extension
timescaledbexists. - If present, executes:
create_hypertable('portfolio_ledger', 'recorded_at', if_not_exists => TRUE)
Implication:
portfolio_ledgeracts as a hypertable in environments with TimescaleDB extension enabled.
7. Integrity Notes
Current baseline intentionally has no DB-level FK constraints.
- Logical relations are managed by application/repository logic.
- When adding FK constraints in future migrations, update this document and include migration IDs.
8. Change Management Rule
When schema changes:
- Add/modify Alembic revision under
migrations/versions. - Update this document in the same commit.
- Keep revision mapping table below in sync.
9. Revision Mapping
| Revision ID | File | Summary |
|---|---|---|
| 20260403_0001 | migrations/versions/20260403_0001_initial_portfolio_schema.py | Initial schema for strategy sets, portfolios, ledger, indexes, optional hypertable |
| 20260403_0002 | migrations/versions/20260403_0002_strategy_set_columns.py | Rename strategy_name->name, strategy_config->config, add unique nick, handle legacy unnamed unique constraint |
| 20260403_0003 | migrations/versions/20260403_0003_portfolio_strategy_refs.py | Remove embedded portfolio strategy fields, add active_strategy_id and avabile_strategies |
| 20260403_0004 | migrations/versions/20260403_0004_users_table.py | Add users table with unique username/email and created_at index |
| 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_0007 | migrations/versions/20260403_0007_bot_principal_permission_only.py | Remove bot_principals.owner_user_id and enforce bot-local permission model |