Skip to content

🏗️ Portfolio Tracker (PostgreSQL) — Architecture Documentation


Architecture Classification

This application is a full-stack Flask web application with a JSON API layer, backed by a managed AWS RDS PostgreSQL database.

Question Answer
What is this application? A multi-page Flask web application with server-rendered templates and a JSON API for interactive Chart.js/TradingView dashboards
How is it deployed? As a Gunicorn WSGI service managed by systemd on AWS EC2, connecting to RDS PostgreSQL in a private subnet
What pattern does it use? Application factory with blueprints (auth, portfolio), request-scoped DB connections, and CLI data-loading commands

Diagram 1 — Software Component Architecture

Shows all internal modules, their dependencies, third-party libraries, and external systems.

graph TB
    subgraph "Python Package: app_portfolio_tracker_aurora"
        INIT["__init__.py<br/><i>Application Factory</i><br/>create_app() · Blueprints<br/>CLI commands · Health check"]
        AUTH["auth.py<br/><i>Authentication Blueprint</i><br/>/auth/register · /auth/login<br/>session management"]
        PORTFOLIO["portfolio.py<br/><i>Portfolio Blueprint</i><br/>Dashboard · Analytics<br/>11 JSON API endpoints"]
        DB["db.py<br/><i>Database Layer</i><br/>Connection pool (psycopg2)<br/>CSV parsers · CLI commands"]
        CALC["calculations.py<br/><i>Financial Engine</i><br/>XIRR · NAV · Allocation<br/>Monthly returns · Calendar"]
        MARKET["market_data.py<br/><i>Market Data Service</i><br/>Yahoo Finance integration<br/>Price & FX rate cache"]
        CONFIG["config.py<br/><i>Configuration</i><br/>Environment-based settings<br/>Session · Upload limits"]
        SCHEMA["schema.sql<br/><i>Database Schema</i><br/>8 tables · 3 views<br/>12 indexes"]
    end

    subgraph "Third-Party Libraries"
        LIB_FLASK["🌐 Flask 3.x<br/><i>Web framework</i>"]
        LIB_PSYCOPG["🐘 psycopg2<br/><i>PostgreSQL driver</i>"]
        LIB_YF["📈 yfinance<br/><i>Yahoo Finance API</i>"]
        LIB_SCIPY["🔬 scipy<br/><i>XIRR optimisation</i>"]
        LIB_PYTZ["🕐 pytz<br/><i>Timezone conversions</i>"]
        LIB_DOTENV["📄 python-dotenv<br/><i>.env file loader</i>"]
        LIB_WERKZEUG["🔐 Werkzeug<br/><i>Password hashing</i>"]
        LIB_GUNICORN["🦄 Gunicorn<br/><i>WSGI server</i>"]
    end

    subgraph "Frontend (CDN Libraries)"
        FE_CHARTJS["📊 Chart.js<br/><i>Pie · Doughnut · Bar</i>"]
        FE_TV["📈 TradingView<br/>Lightweight Charts<br/><i>Time-series lines</i>"]
        FE_JINJA["📄 Jinja2 Templates<br/><i>Server-side rendering</i>"]
    end

    subgraph "External Services"
        RDS_PG["🐘 AWS RDS PostgreSQL<br/><i>Private subnet · Port 5432</i>"]
        YAHOO["📈 Yahoo Finance API<br/><i>HTTPS — prices & FX rates</i>"]
    end

    subgraph "Local Filesystem"
        FS_ENV[".env<br/><i>DATABASE_URL · SECRET_KEY</i>"]
        FS_CSV["csv_data/<br/><i>Broker CSV/TXT files</i>"]
    end

    %% Internal dependencies
    INIT -->|"registers"| AUTH
    INIT -->|"registers"| PORTFOLIO
    INIT -->|"initialises"| DB
    INIT -->|"registers CLI"| MARKET
    PORTFOLIO -->|"imports"| CALC
    PORTFOLIO -->|"imports"| DB
    AUTH -->|"imports"| DB
    CALC -->|"imports"| MARKET
    CALC -->|"imports"| DB
    MARKET -->|"imports"| DB
    DB -->|"applies"| SCHEMA
    INIT -->|"loads"| CONFIG

    %% Library usage
    INIT -.-|"uses"| LIB_FLASK
    INIT -.-|"uses"| LIB_DOTENV
    DB -.-|"uses"| LIB_PSYCOPG
    DB -.-|"uses"| LIB_PYTZ
    MARKET -.-|"uses"| LIB_YF
    CALC -.-|"uses"| LIB_SCIPY
    AUTH -.-|"uses"| LIB_WERKZEUG

    %% Frontend
    PORTFOLIO -->|"renders"| FE_JINJA
    FE_JINJA -->|"includes"| FE_CHARTJS
    FE_JINJA -->|"includes"| FE_TV

    %% External system interactions
    DB -->|"psycopg2<br/>connection"| RDS_PG
    MARKET -->|"yfinance<br/>HTTPS API"| YAHOO

    %% Filesystem
    CONFIG -->|"reads"| FS_ENV
    DB -->|"reads"| FS_CSV

    %% Styling
    classDef module fill:#4A90D9,stroke:#2C5F8A,color:#fff,stroke-width:2px
    classDef lib fill:#F5A623,stroke:#C77D0A,color:#fff,stroke-width:2px
    classDef frontend fill:#9B59B6,stroke:#7D3C98,color:#fff,stroke-width:2px
    classDef external fill:#E74C3C,stroke:#C0392B,color:#fff,stroke-width:2px
    classDef filesystem fill:#27AE60,stroke:#1E8449,color:#fff,stroke-width:2px

    class INIT,AUTH,PORTFOLIO,DB,CALC,MARKET,CONFIG,SCHEMA module
    class LIB_FLASK,LIB_PSYCOPG,LIB_YF,LIB_SCIPY,LIB_PYTZ,LIB_DOTENV,LIB_WERKZEUG,LIB_GUNICORN lib
    class FE_CHARTJS,FE_TV,FE_JINJA frontend
    class RDS_PG,YAHOO external
    class FS_ENV,FS_CSV filesystem

Diagram 2 — Request Lifecycle Sequence Diagram

Shows the full request flow when a user loads the interactive dashboard, including API calls for chart data.

sequenceDiagram
    autonumber
    participant USER as 👤 Browser
    participant GUN as 🦄 Gunicorn<br/>(WSGI)
    participant FLASK as 🌐 Flask<br/>(portfolio.py)
    participant AUTH as 🔐 Auth<br/>(auth.py)
    participant JINJA as 📄 Jinja2<br/>Templates
    participant CALC as 🔬 Calculations<br/>(calculations.py)
    participant MARKET as 📈 Market Data<br/>(market_data.py)
    participant DB as 🐘 psycopg2
    participant RDS as ☁️ RDS<br/>PostgreSQL
    participant YAHOO as 📊 Yahoo<br/>Finance API

    Note over USER,GUN: Phase 1 — Page Load

    USER->>GUN: GET /dashboard
    GUN->>FLASK: WSGI dispatch
    FLASK->>AUTH: @bp.before_app_request<br/>load_logged_in_user()
    AUTH->>DB: SELECT * FROM "user" WHERE id = %s
    DB->>RDS: SQL query
    RDS-->>DB: User row
    DB-->>AUTH: g.user = {...}
    FLASK->>JINJA: render_template('portfolio/dashboard.html')
    JINJA-->>FLASK: HTML with Chart.js + TradingView JS
    FLASK-->>GUN: HTTP 200 + HTML
    GUN-->>USER: Dashboard page

    Note over USER,YAHOO: Phase 2 — Async API Calls (JavaScript fetch)

    par Performance Summary
        USER->>GUN: GET /api/performance-summary?currency=SGD
        GUN->>FLASK: Route dispatch
        FLASK->>CALC: get_performance_summary(base_currency='SGD')
        CALC->>DB: SELECT holdings, cash flows, transactions
        DB->>RDS: Multiple SQL queries
        RDS-->>DB: Result sets
        CALC->>MARKET: get_latest_price(ticker, date)
        MARKET->>DB: SELECT FROM DAILY_PRICE (cache lookup)
        DB->>RDS: Cache query
        RDS-->>DB: Cached price (or miss)
        alt Cache miss
            MARKET->>YAHOO: yfinance.Ticker(ticker).history()
            YAHOO-->>MARKET: OHLCV data
            MARKET->>DB: INSERT INTO DAILY_PRICE
            DB->>RDS: Cache write
        end
        CALC->>MARKET: get_fx_rate('USD', 'SGD', date)
        MARKET->>DB: SELECT FROM FX_RATE (cache lookup)
        DB->>RDS: Cache query
        RDS-->>DB: FX rate
        CALC->>CALC: calculate_portfolio_xirr()<br/>scipy.optimize.brentq()
        CALC-->>FLASK: {total_nav, xirr_pct, ...}
        FLASK-->>USER: JSON response
    and Asset Allocation
        USER->>GUN: GET /api/allocation?currency=SGD
        GUN->>FLASK: Route dispatch
        FLASK->>CALC: get_asset_allocation()
        CALC-->>FLASK: [{ticker, value, percentage}, ...]
        FLASK-->>USER: JSON → Chart.js pie chart
    and NAV History
        USER->>GUN: GET /api/nav-history?currency=SGD&interval=7
        GUN->>FLASK: Route dispatch
        FLASK->>CALC: get_nav_history()
        Note over CALC: Iterates dates at interval,<br/>calculates NAV at each point
        CALC-->>FLASK: [{date, nav, securities_nav, fund_balance}, ...]
        FLASK-->>USER: JSON → TradingView line chart
    end

    Note over USER: Dashboard renders all charts

Diagram 3 — Network & Infrastructure Architecture

Shows the AWS infrastructure layout including VPC, subnets, security groups, and traffic flow as defined in the CloudFormation stack.

graph TB
    subgraph "Developer Machine (Windows)"
        DEV["💻 Developer<br/>PyCharm / VS Code"]
    end

    subgraph "GitHub"
        GH["📦 GitHub Repository<br/>darren2pro/portfolio_tracker"]
    end

    subgraph "Cloudflare"
        CF["🌐 Cloudflare Pages<br/>portfolio.darrenheng.dev<br/><i>MkDocs documentation site</i>"]
    end

    subgraph "AWS Cloud — ap-southeast-1 (Singapore)"
        subgraph "VPC: 10.0.0.0/16"
            subgraph "Public Subnet A: 10.0.0.0/24 (AZ-a)"
                EC2["🖥️ EC2 Instance<br/>t3.large · Amazon Linux 2023<br/><br/>🦄 Gunicorn (port 8080)<br/>🐍 Python 3.13 venv<br/>📦 app_portfolio_tracker_aurora"]
            end

            subgraph "Public Subnet B: 10.0.1.0/24 (AZ-b)"
                PUB_B["<i>(Available for<br/>future scaling)</i>"]
            end

            subgraph "Private Subnet A: 10.0.10.0/24 (AZ-a)"
                RDS_A["🐘 RDS PostgreSQL<br/>db.t3.medium<br/>Engine: PostgreSQL 16<br/>Port 5432"]
            end

            subgraph "Private Subnet B: 10.0.11.0/24 (AZ-b)"
                RDS_B["<i>(Multi-AZ standby<br/>— available if enabled)</i>"]
            end

            IGW["🌐 Internet Gateway"]
        end

        subgraph "Security Groups"
            SG_BASTION["🔒 Bastion SG<br/>Inbound: SSH (22), HTTP (8080)<br/>Outbound: All"]
            SG_DB["🔒 Database SG<br/>Inbound: PostgreSQL (5432)<br/><i>from Bastion SG only</i><br/>Outbound: All"]
        end
    end

    subgraph "External APIs"
        YAHOO["📈 Yahoo Finance<br/>HTTPS (443)"]
    end

    %% Developer connections
    DEV -->|"SSH (port 22)<br/>Key pair auth"| EC2
    DEV -->|"git push"| GH
    GH -->|"git pull<br/>(deploy script)"| EC2

    %% Internet Gateway
    IGW -->|"Public route<br/>0.0.0.0/0"| EC2

    %% EC2 → Database (private)
    EC2 -->|"PostgreSQL (5432)<br/>psycopg2 · Same VPC"| RDS_A

    %% Security Group assignments
    SG_BASTION -.->|"attached to"| EC2
    SG_DB -.->|"attached to"| RDS_A

    %% EC2 → External APIs
    EC2 -->|"HTTPS (443)<br/>yfinance"| YAHOO

    %% Cloudflare
    DEV -->|"mkdocs build<br/>wrangler pages deploy"| CF

    %% No NAT
    RDS_A -.->|"❌ No internet access<br/>(no NAT Gateway)"| IGW

    %% Styling
    classDef dev fill:#5B9BD5,stroke:#2E75B6,color:#fff,stroke-width:2px
    classDef github fill:#333,stroke:#555,color:#fff,stroke-width:2px
    classDef ec2 fill:#FF9900,stroke:#CC7A00,color:#fff,stroke-width:2px
    classDef rds fill:#3B48CC,stroke:#2A35A0,color:#fff,stroke-width:2px
    classDef sg fill:#E67E22,stroke:#CA6F1E,color:#fff,stroke-width:2px
    classDef ext fill:#E74C3C,stroke:#C0392B,color:#fff,stroke-width:2px
    classDef cf fill:#F48120,stroke:#D06E1A,color:#fff,stroke-width:2px
    classDef placeholder fill:#95A5A6,stroke:#7F8C8D,color:#fff,stroke-width:1px

    class DEV dev
    class GH github
    class EC2 ec2
    class RDS_A rds
    class SG_BASTION,SG_DB sg
    class YAHOO ext
    class CF cf
    class IGW ec2
    class PUB_B,RDS_B placeholder

Network Security Summary

Rule Source Destination Port Purpose
SSH access Developer IP EC2 Bastion 22 Remote administration
Flask app Allowed CIDR EC2 Bastion 8080 Web application access
Database Bastion SG only RDS PostgreSQL 5432 Application → DB queries
Yahoo Finance EC2 (outbound) api.yahoo.com 443 Market data fetch
No NAT Gateway Cost saving — RDS has no internet

Diagram 4 — Data Pipeline & ETL Flow

Shows how investment data flows from broker platforms through CSV parsing into the database, then through the calculation engine to the frontend visualisations.

flowchart LR
    subgraph "Data Sources"
        MOOMOO_SEC["📄 Moomoo Securities<br/>CSV Export<br/><i>Stocks · Options · ETFs</i>"]
        MOOMOO_FUND["📄 Moomoo Funds<br/>CSV Export<br/><i>Money Market Funds</i>"]
        MOOMOO_DEP["📄 Moomoo Deposits<br/>TXT Export<br/><i>External cash inflows</i>"]
        MOOMOO_WD["📄 Moomoo Withdrawals<br/>TXT Export<br/><i>External cash outflows</i>"]
        YAHOO_API["📈 Yahoo Finance<br/>API<br/><i>Prices · FX Rates</i>"]
    end

    subgraph "Ingestion Layer (db.py)"
        PARSE_SEC["handle_moomoo_securities()<br/><i>Parse filled orders</i><br/><i>Handle partial fills</i><br/><i>Detect asset types</i><br/><i>Convert timezones</i>"]
        PARSE_FUND["handle_moomoo_funds()<br/><i>Parse subscribe/redeem</i><br/><i>Extract amount+currency</i>"]
        PARSE_FLOW["handle_moomoo_external_flows()<br/><i>Parse 3-line TXT blocks</i><br/><i>DD/MM/YYYY dates</i>"]
        LOAD_ORCH["load_csv_data()<br/><i>Orchestrator</i><br/><i>Scans csv_data/ directory</i><br/><i>Routes files to handlers</i>"]
    end

    subgraph "Market Data Layer (market_data.py)"
        FETCH_PRICES["fetch_historical_prices()<br/><i>Smart caching — skip existing dates</i><br/><i>OHLCV → DAILY_PRICE table</i>"]
        FETCH_FX["fetch_fx_rates()<br/><i>SGD/USD + USD/SGD pairs</i><br/><i>Daily close → FX_RATE table</i>"]
    end

    subgraph "RDS PostgreSQL"
        T_BROKER[("BROKER<br/><i>Moomoo · Tiger · WeBull</i>")]
        T_TRANS[("TRANSACTION<br/><i>Securities trades</i>")]
        T_FUND[("FUND_TRANSACTION<br/><i>MMF subscribe/redeem</i>")]
        T_CASH[("EXTERNAL_CASH_FLOW<br/><i>Deposits/withdrawals</i>")]
        T_PRICE[("DAILY_PRICE<br/><i>OHLCV cache</i>")]
        T_FX[("FX_RATE<br/><i>Currency rates</i>")]
        T_SNAP[("BALANCE_SNAPSHOT<br/><i>Manual overrides</i>")]
        V_HOLD[("VIEW: PORTFOLIO_HOLDINGS<br/><i>Net qty · Avg cost</i>")]
        V_SUMMARY[("VIEW: TRANSACTION_SUMMARY<br/><i>Aggregated by ticker</i>")]
        V_FBAL[("VIEW: FUND_BALANCE_SUMMARY<br/><i>Net fund balance</i>")]
    end

    subgraph "Calculation Engine (calculations.py)"
        CALC_NAV["calculate_securities_nav()<br/><i>Holdings × Price × FX</i>"]
        CALC_FUND["calculate_fund_balance()<br/><i>Subscribe − Redeem</i>"]
        CALC_XIRR["calculate_portfolio_xirr()<br/><i>scipy.optimize.brentq</i>"]
        CALC_ALLOC["get_asset_allocation()<br/>get_currency_allocation()"]
        CALC_HIST["get_nav_history()<br/><i>Weekly NAV snapshots</i>"]
        CALC_RET["get_monthly_returns()<br/><i>Cash-flow adjusted</i>"]
    end

    subgraph "API Layer (portfolio.py)"
        API["/api/performance-summary<br/>/api/nav-history<br/>/api/allocation<br/>/api/currency-allocation<br/>/api/monthly-returns<br/>/api/irr<br/>/api/holdings<br/>/api/fund-balance<br/>/api/cash-flows<br/>/api/transactions<br/>/api/transactions-calendar"]
    end

    subgraph "Frontend Visualisation"
        STAT["📊 Stat Cards<br/><i>NAV · XIRR · Return %</i>"]
        PIE["🥧 Pie Chart<br/><i>Asset allocation</i>"]
        DONUT["🍩 Doughnut Chart<br/><i>Currency allocation</i>"]
        BAR["📊 Bar Chart<br/><i>Monthly returns</i>"]
        LINE["📈 Line Chart<br/><i>NAV time series</i>"]
        HEAT["🗓️ Calendar Heatmap<br/><i>Transaction activity</i>"]
        TABLE["📋 Data Table<br/><i>Recent transactions</i>"]
    end

    %% Source → Ingestion
    MOOMOO_SEC --> PARSE_SEC
    MOOMOO_FUND --> PARSE_FUND
    MOOMOO_DEP --> PARSE_FLOW
    MOOMOO_WD --> PARSE_FLOW
    LOAD_ORCH --> PARSE_SEC
    LOAD_ORCH --> PARSE_FUND
    LOAD_ORCH --> PARSE_FLOW

    %% Market data
    YAHOO_API --> FETCH_PRICES
    YAHOO_API --> FETCH_FX

    %% Ingestion → Database
    PARSE_SEC --> T_TRANS
    PARSE_FUND --> T_FUND
    PARSE_FLOW --> T_CASH
    FETCH_PRICES --> T_PRICE
    FETCH_FX --> T_FX

    %% Database views
    T_TRANS --> V_HOLD
    T_TRANS --> V_SUMMARY
    T_FUND --> V_FBAL

    %% Calculations ← Database
    T_TRANS --> CALC_NAV
    T_FUND --> CALC_FUND
    T_CASH --> CALC_XIRR
    T_PRICE --> CALC_NAV
    T_FX --> CALC_NAV
    T_FX --> CALC_FUND
    T_FX --> CALC_XIRR
    T_SNAP --> CALC_FUND

    CALC_NAV --> CALC_ALLOC
    CALC_FUND --> CALC_ALLOC
    CALC_NAV --> CALC_HIST
    CALC_FUND --> CALC_HIST
    CALC_NAV --> CALC_RET

    %% Calc → API
    CALC_NAV --> API
    CALC_FUND --> API
    CALC_XIRR --> API
    CALC_ALLOC --> API
    CALC_HIST --> API
    CALC_RET --> API

    %% API → Frontend
    API --> STAT
    API --> PIE
    API --> DONUT
    API --> BAR
    API --> LINE
    API --> HEAT
    API --> TABLE

    %% Styling
    classDef source fill:#3498DB,stroke:#2471A3,color:#fff,stroke-width:2px
    classDef ingest fill:#E67E22,stroke:#CA6F1E,color:#fff,stroke-width:2px
    classDef db fill:#2ECC71,stroke:#27AE60,color:#fff,stroke-width:2px
    classDef calc fill:#9B59B6,stroke:#7D3C98,color:#fff,stroke-width:2px
    classDef api fill:#1ABC9C,stroke:#16A085,color:#fff,stroke-width:2px
    classDef viz fill:#E74C3C,stroke:#C0392B,color:#fff,stroke-width:2px

    class MOOMOO_SEC,MOOMOO_FUND,MOOMOO_DEP,MOOMOO_WD,YAHOO_API source
    class PARSE_SEC,PARSE_FUND,PARSE_FLOW,LOAD_ORCH,FETCH_PRICES,FETCH_FX ingest
    class T_BROKER,T_TRANS,T_FUND,T_CASH,T_PRICE,T_FX,T_SNAP,V_HOLD,V_SUMMARY,V_FBAL db
    class CALC_NAV,CALC_FUND,CALC_XIRR,CALC_ALLOC,CALC_HIST,CALC_RET calc
    class API api
    class STAT,PIE,DONUT,BAR,LINE,HEAT,TABLE viz

Diagram 5 — Database Entity-Relationship Diagram

Shows the complete database schema with all tables, columns, constraints, and relationships.

erDiagram
    BROKER {
        SERIAL id PK
        VARCHAR name UK "Moomoo, Tiger, WeBull"
        TEXT description
        TIMESTAMPTZ created_at
    }

    PORTFOLIO {
        SERIAL id PK
        VARCHAR name "Main Portfolio"
        TEXT description
        TIMESTAMPTZ created_at
    }

    TRANSACTION {
        SERIAL id PK
        INTEGER portfolio_id FK
        INTEGER broker_id FK
        VARCHAR ticker "AAPL, NVDA, VOO"
        VARCHAR ticker_name "Apple Inc."
        VARCHAR side "Buy, Sell, Short Sell"
        VARCHAR asset_type "Stock, Option, ETF"
        TIMESTAMPTZ order_date
        DECIMAL order_price
        DECIMAL order_qty
        DECIMAL order_amt
        VARCHAR order_type
        TIMESTAMPTZ fill_date
        DECIMAL fill_price
        DECIMAL fill_qty
        DECIMAL fill_amt
        VARCHAR market "US, SG, HK"
        VARCHAR currency "USD, SGD"
        DECIMAL calculated_fees_sum
        DECIMAL total_fees
        TEXT notes
        VARCHAR source_file
        TIMESTAMPTZ created_at
    }

    FUND_TRANSACTION {
        SERIAL id PK
        INTEGER broker_id FK
        VARCHAR side "Subscribe, Redeem"
        VARCHAR fund_name
        DECIMAL amount
        DECIMAL units
        VARCHAR currency
        TIMESTAMPTZ order_time
        VARCHAR source_file
        TIMESTAMPTZ created_at
    }

    EXTERNAL_CASH_FLOW {
        SERIAL id PK
        INTEGER broker_id FK
        VARCHAR flow_type "Deposit, Withdrawal"
        DECIMAL amount
        VARCHAR currency
        DATE flow_date
        VARCHAR source_file
        TIMESTAMPTZ created_at
    }

    BALANCE_SNAPSHOT {
        SERIAL id PK
        INTEGER broker_id FK
        VARCHAR snapshot_type "MMF, Securities, Cash, Total"
        VARCHAR description
        DECIMAL amount
        VARCHAR currency
        DATE snapshot_date
        TIMESTAMPTZ created_at
    }

    DAILY_PRICE {
        SERIAL id PK
        VARCHAR ticker
        DATE date UK "UNIQUE(ticker, date)"
        DECIMAL open
        DECIMAL high
        DECIMAL low
        DECIMAL close
        DECIMAL adj_close
        BIGINT volume
        VARCHAR currency
        VARCHAR source "yfinance"
        TIMESTAMPTZ created_at
    }

    FX_RATE {
        SERIAL id PK
        VARCHAR from_currency
        VARCHAR to_currency
        DATE date UK "UNIQUE(from, to, date)"
        DECIMAL rate
        VARCHAR source "yfinance"
        TIMESTAMPTZ created_at
    }

    BROKER ||--o{ TRANSACTION : "has many"
    BROKER ||--o{ FUND_TRANSACTION : "has many"
    BROKER ||--o{ EXTERNAL_CASH_FLOW : "has many"
    BROKER ||--o{ BALANCE_SNAPSHOT : "has many"
    PORTFOLIO ||--o{ TRANSACTION : "groups"

Diagram 6 — Deployment & CI/CD Sequence

Shows the deployment workflow from code change to live application.

sequenceDiagram
    autonumber
    participant DEV as 💻 Developer<br/>(Windows)
    participant GH as 📦 GitHub
    participant EC2 as 🖥️ EC2 Instance<br/>(Amazon Linux)
    participant SYS as 🔧 systemd
    participant GUN as 🦄 Gunicorn
    participant RDS as 🐘 RDS<br/>PostgreSQL

    Note over DEV,GH: Code Change & Push

    DEV->>DEV: Edit code locally
    DEV->>GH: git push origin master

    Note over DEV,EC2: Deployment

    DEV->>EC2: SSH into EC2
    EC2->>EC2: cd ~/portfolio_tracker
    EC2->>GH: git pull origin master
    GH-->>EC2: Latest code
    EC2->>EC2: source venv/bin/activate
    EC2->>EC2: pip install -r requirements.txt

    Note over EC2,RDS: Database Update (if needed)

    opt Schema changes
        EC2->>EC2: flask --app app_portfolio_tracker_aurora init-db
        EC2->>RDS: DROP + CREATE tables, views, indexes
        RDS-->>EC2: Schema applied
    end

    opt CSV data reload
        EC2->>EC2: flask --app app_portfolio_tracker_aurora load-csv
        EC2->>RDS: INSERT INTO TRANSACTION, FUND_TRANSACTION, ...
        RDS-->>EC2: Data loaded
    end

    opt Market data refresh
        EC2->>EC2: flask --app app_portfolio_tracker_aurora fetch-prices
        EC2->>EC2: flask --app app_portfolio_tracker_aurora fetch-fx-rates
    end

    Note over EC2,GUN: Service Restart

    EC2->>SYS: sudo systemctl daemon-reload
    EC2->>SYS: sudo systemctl restart portfolio-tracker
    SYS->>GUN: ExecStart: gunicorn -w 4 -b 0.0.0.0:8080
    GUN->>GUN: Fork 4 worker processes
    GUN->>RDS: Test connection (health check)
    RDS-->>GUN: Connection OK

    Note over GUN: Application live on port 8080

    SYS-->>EC2: Service active (running)

Diagram 7 — CSV Ingestion Pipeline (Detailed)

Shows the detailed parsing flow for Moomoo broker files, including partial fill handling and timezone conversion.

flowchart TD
    START(["flask --app app_portfolio_tracker_aurora load-csv"]) --> SCAN["Scan csv_data/ directory<br/>for all files"]

    SCAN --> MATCH{"Match file<br/>patterns"}

    MATCH -->|"*Securities*.csv"| SEC_HANDLER
    MATCH -->|"*Funds*.csv"| FUND_HANDLER
    MATCH -->|"*Deposit*.txt"| DEP_HANDLER
    MATCH -->|"*Withdrawal*.txt"| WD_HANDLER

    subgraph "Securities CSV Handler"
        SEC_HANDLER["handle_moomoo_securities()"]
        SEC_HANDLER --> SEC_READ["Read CSV with DictReader<br/>(UTF-8 BOM handling)"]
        SEC_READ --> SEC_LOOP["For each row"]
        SEC_LOOP --> SEC_STATUS{"Status =<br/>'Filled'?"}
        SEC_STATUS -->|"No"| SEC_SKIP["Skip row"]
        SEC_STATUS -->|"Yes"| SEC_CHECK{"Side & Ticker<br/>empty?"}
        SEC_CHECK -->|"Yes"| SEC_PARTIAL["Partial fill continuation<br/>Attach to parent order"]
        SEC_CHECK -->|"No"| SEC_PARSE["Parse order + fill data<br/>14 fee columns<br/>Asset type detection"]
        SEC_PARSE --> SEC_TZ["convert_to_psql_timestamp()<br/>'Mar 28, 2026 01:21:45 ET'<br/>→ 2026-03-28T14:21:45+08:00"]
        SEC_TZ --> SEC_TYPE["_detect_asset_type()<br/>NVDA → Stock<br/>VOO → ETF<br/>NVDA260327P140000 → Option"]
        SEC_TYPE --> SEC_INSERT["INSERT INTO TRANSACTION"]
        SEC_PARTIAL --> SEC_INSERT
    end

    subgraph "Funds CSV Handler"
        FUND_HANDLER["handle_moomoo_funds()"]
        FUND_HANDLER --> FUND_READ["Read CSV"]
        FUND_READ --> FUND_STATUS{"Status =<br/>'Completed'?"}
        FUND_STATUS -->|"Yes"| FUND_PARSE["Parse Amount/Units<br/>'212.61USD' → 212.61"]
        FUND_PARSE --> FUND_INSERT["INSERT INTO FUND_TRANSACTION"]
    end

    subgraph "External Flow Handler"
        DEP_HANDLER["handle_moomoo_external_flows()<br/>(flow_type='Deposit')"]
        WD_HANDLER["handle_moomoo_external_flows()<br/>(flow_type='Withdrawal')"]
        DEP_HANDLER --> TXT_READ
        WD_HANDLER --> TXT_READ
        TXT_READ["Read 3-line blocks<br/>Line 1: '3,800.00 SGD'<br/>Line 2: '27/01/2026'<br/>Line 3: 'Completed'"]
        TXT_READ --> TXT_STATUS{"Status =<br/>'Completed'?"}
        TXT_STATUS -->|"Yes"| TXT_INSERT["INSERT INTO EXTERNAL_CASH_FLOW"]
    end

    SEC_INSERT --> COMMIT["db.commit()"]
    FUND_INSERT --> COMMIT
    TXT_INSERT --> COMMIT

    COMMIT --> DONE(["✅ Data loaded successfully"])

    %% Styling
    classDef start fill:#3498DB,stroke:#2471A3,color:#fff,stroke-width:2px
    classDef handler fill:#E67E22,stroke:#CA6F1E,color:#fff,stroke-width:2px
    classDef decision fill:#9B59B6,stroke:#7D3C98,color:#fff,stroke-width:2px
    classDef action fill:#27AE60,stroke:#1E8449,color:#fff,stroke-width:2px
    classDef skip fill:#E74C3C,stroke:#C0392B,color:#fff,stroke-width:2px

    class START,DONE start
    class SEC_HANDLER,FUND_HANDLER,DEP_HANDLER,WD_HANDLER handler
    class SEC_STATUS,SEC_CHECK,FUND_STATUS,TXT_STATUS,MATCH decision
    class SEC_PARSE,SEC_TZ,SEC_TYPE,SEC_INSERT,FUND_PARSE,FUND_INSERT,TXT_READ,TXT_INSERT,COMMIT action
    class SEC_SKIP skip

Technology Stack Summary

Layer Technology Purpose
Language Python 3.13 Application runtime
Web Framework Flask 3.x Routes, blueprints, templates, JSON API
WSGI Server Gunicorn Production multi-worker process management
Database AWS RDS PostgreSQL 16 Managed single-instance relational database
DB Driver psycopg2 (RealDictCursor) PostgreSQL wire protocol adapter
Charts Chart.js (CDN) Pie, doughnut, and bar charts
Financial Charts TradingView Lightweight Charts (CDN) Time-series NAV line charts
Templates Jinja2 Server-side HTML rendering
Market Data yfinance (Yahoo Finance) Historical prices and FX rates
XIRR Engine scipy.optimize Brent's method / Newton's method root finding
Timezone pytz Multi-timezone date conversion (ET, SGT, UTC)
Auth Werkzeug Password hashing (pbkdf2:sha256)
Configuration python-dotenv Environment variable loading from .env
Process Management systemd Daemon lifecycle, auto-restart, boot persistence
Infrastructure AWS CloudFormation VPC, subnets, EC2, RDS — infrastructure as code
Documentation MkDocs Material + Mermaid Architecture diagrams published on Cloudflare Pages
Source Control Git + GitHub Version control