Portfolio Module
Reference
Every portfolio endpoint, DTO, cache key, and SQL query at a glance
A single-page lookup table for the portfolio module — endpoints, DTOs, cache keys, sqlc queries, and notable file paths.
Every path is prefixed with /api/v1. All require JWT.
| Method | Path | Handler | Status |
|---|
POST | /portfolios | core.Handler.Create | 201 |
GET | /portfolios | core.Handler.List | 200 |
GET | /portfolios/{id} | core.Handler.Get | 200 |
GET | /portfolios/{id}/summary | core.Handler.GetSummary | 200 |
GET | /portfolios/{id}/valuation | core.Handler.GetValuation | 200 |
GET | /portfolios/{id}/broker-analysis | core.Handler.GetBrokerAnalysis | 200 |
PATCH | /portfolios/{id} | core.Handler.Update | 200 |
DELETE | /portfolios/{id} | core.Handler.Delete | 204 |
| Method | Path | Handler |
|---|
GET | /portfolios/{id}/holdings | companies.Handler.ListHoldings |
GET | /portfolios/{id}/distribution | companies.Handler.GetDistribution |
GET | /portfolios/{id}/companies/{symbol} | companies.Handler.GetCompanyDetail |
| Method | Path | Handler |
|---|
POST | /portfolios/{id}/trades | trades.Handler.AddTrade |
GET | /portfolios/{id}/trades | trades.Handler.ListTrades |
PATCH | /portfolios/{id}/trades/{tradeId} | trades.Handler.UpdateTrade |
DELETE | /portfolios/{id}/trades/{tradeId} | trades.Handler.DeleteTrade |
| Method | Path | Handler |
|---|
POST | /portfolios/{id}/companies/{symbol}/dividends | dividends.Handler.AddDividend |
GET | /portfolios/{id}/companies/{symbol}/dividends | dividends.Handler.ListCompanyDividends |
GET | /portfolios/{id}/dividends | dividends.Handler.ListPortfolioDividends |
PATCH | /portfolios/{id}/companies/{symbol}/dividends/{dividendId} | dividends.Handler.UpdateDividend |
DELETE | /portfolios/{id}/companies/{symbol}/dividends/{dividendId} | dividends.Handler.DeleteDividend |
| Method | Path | Handler |
|---|
POST | /portfolios/{id}/companies/{symbol}/actions | actions.Handler.AddCorporateAction |
GET | /portfolios/{id}/companies/{symbol}/actions | actions.Handler.ListCorporateActions |
PATCH | /portfolios/{id}/companies/{symbol}/actions/{actionId} | actions.Handler.UpdateCorporateAction |
DELETE | /portfolios/{id}/companies/{symbol}/actions/{actionId} | actions.Handler.DeleteCorporateAction |
| Type | Used by |
|---|
core.CreatePortfolioRequest | POST /portfolios |
core.UpdatePortfolioRequest | PATCH /portfolios/{id} |
trades.AddTradeRequest | POST /portfolios/{id}/trades |
trades.UpdateTradeRequest | PATCH /portfolios/{id}/trades/{tradeId} |
dividends.CreateDividendRequest | POST /portfolios/{id}/companies/{symbol}/dividends |
dividends.UpdateDividendRequest | PATCH .../dividends/{dividendId} |
actions.CreateCorporateActionRequest | POST /portfolios/{id}/companies/{symbol}/actions |
actions.UpdateCorporateActionRequest | PATCH .../actions/{actionId} |
| Type | Returned by |
|---|
core.Response | Create, List items, Update |
core.DetailResponse | Get |
core.SummaryResponse | GetSummary; embedded in DetailResponse |
core.SuspendedSummary | embedded in DetailResponse |
core.DistributionBlock + core.SectorBucket | embedded in DetailResponse |
core.Position | embedded in DetailResponse (positions[]) |
core.ValuationResponse + core.ValuationPoint | GetValuation |
core.BrokerAnalysisResponse + BrokerAnalysisBucket + BrokerAnalysisItem + BrokerSignalCounts | GetBrokerAnalysis |
holdings.HoldingResponse | ListHoldings (the full holdings table) |
companies.DistributionResponse + DistributionItem | GetDistribution |
companies.CompanyPortfolioDetailResponse | GetCompanyDetail |
companies.CompanySnapshotResponse | embedded in CompanyPortfolioDetailResponse |
companies.CompanyHoldingResponse | embedded; carries WACCBreakdownResponse |
companies.WACCBreakdownResponse | embedded |
companies.FIFOLotResponse | embedded (when lots=true) |
companies.PortfolioTimelineEvent | embedded |
trades.TradeResponse | all 4 trade endpoints |
dividends.DividendResponse | all 5 dividend endpoints |
actions.CorporateActionResponse | all 4 action endpoints |
| Key | TTL | Defined in |
|---|
portfolio:detail:<portfolioID> | 15s | cache.PortfolioDetailKey |
portfolio:valuation:<portfolioID>:<RANGE> | 10min | cache.PortfolioValuationKey |
<RANGE> ∈ {1M, 3M, 6M, 1Y, YTD, ALL}.
Located in internal/platform/database/queries/. Each tag package's Store interface lists the methods it actually calls.
portfolios.sql:
CountPortfoliosByCustomer
FindPortfoliosByCustomer
FindPortfolioByID (ownership guard)
FindHoldingsWithPricesByPortfolio
FindPortfolioTradesInRange (today's trades for day P&L)
TouchPortfolioFetchedAt
FindAllPortfolioTradesForValuation
FindAllPortfolioCorporateActionsForValuation
FindBulkPriceHistoryForPortfolio (ANY($1::uuid[]))
portfolio_companies.sql:
FindPortfolioCompanyBySymbol
FindHoldingWithPriceByCompany
FindOpenLotsByCompany
portfolio_trades.sql:
FindTradesByPortfolio
CountTradesByPortfolio
portfolio_dividends.sql:
FindDividendsByCompany
FindDividendsByPortfolio
portfolio_corporate_actions.sql:
FindCorporateActionsByCompany
portfolios.sql:
CreatePortfolio
UpdatePortfolio
SoftDeletePortfolio
SoftDeleteTradesByPortfolio
SoftDeleteDividendsByPortfolio
SoftDeleteCorporateActionsByPortfolio
DeletePortfolioLotConsumptionsByPortfolio
DeletePortfolioLotsByPortfolio
DeletePortfolioHoldingsByPortfolio
portfolio_trades.sql:
AddTrade
FindTradeForUpdate
UpdateTrade
SoftDeleteTrade
UpdateTradeAudit (called after Rebuild)
FindSettlementDate
IsTradingDay
FindCurrentHoldingForUpdate
portfolio_dividends.sql:
CreateDividend
UpdateDividend
SoftDeleteDividend
portfolio_corporate_actions.sql:
CreateCorporateAction
FindCorporateActionForUpdate
UpdateCorporateAction
SoftDeleteCorporateAction
portfolio_holdings.sql:
DeleteLotConsumptionsForHolding
DeleteLotsForHolding
FindPortfolioTradesForReplay
FindDividendsForReplay
FindCorporateActionsForReplay
CreatePortfolioLot
RecordLotConsumption
UpsertPortfolioHolding
FindCurrentHoldingForUpdate
internal/modules/portfolio/
├── portfolio.go # Module + RegisterRoutes
├── smoke_db_test.go
├── shared/
│ ├── cache.go # CacheScope, Invalidator, TxRunner
│ ├── conversions.go # ParseTradeTime, ParseDate, DateFromStringPtr,
│ │ # DateToStringPtr, StringPtrNonEmpty
│ ├── guard.go # FindPortfolio
│ └── store.go # PortfolioLookup
├── holdings/
│ ├── replay.go # Rebuild + CurrentUnitsForHolding
│ ├── replay_types.go # replayLot, replayConsumption, replayEvent
│ ├── daypnl.go # DayPnL + sameDayBuyLot
│ ├── view.go # HoldingResponse + HoldingFromRow
│ ├── store.go
│ └── holdings_test.go
├── core/ # tag: Portfolio
│ ├── service.go # 8 methods
│ ├── handler.go # 7 handlers
│ ├── types.go
│ ├── valuation.go # buildValuation + range helpers
│ ├── mappers.go # portfolioToDTO, detailToDTO
│ ├── store.go
│ ├── handler_test.go
│ └── valuation_test.go
├── companies/ # tag: Portfolio Companies
│ ├── service.go # 3 methods
│ ├── handler.go # 3 handlers
│ ├── types.go
│ ├── distribution.go # buildDistribution
│ ├── mappers.go # 9 helpers
│ ├── store.go
│ ├── handler_test.go
│ └── distribution_test.go
├── trades/ # tag: Portfolio Trades
│ ├── service.go # 4 methods
│ ├── handler.go # 4 handlers + parseTradeFilter
│ ├── types.go # AddTradeRequest, UpdateTradeRequest, TradeResponse, TradeFilter
│ ├── calc.go # broker rate, fees, totals, mergeTradeUpdate, computeCgtMismatch, tradedAtWarnings
│ ├── mappers.go # 3 row→DTO mappers
│ ├── store.go
│ ├── handler_test.go
│ └── calc_test.go
├── dividends/ # tag: Portfolio Dividends
│ ├── service.go # 5 methods
│ ├── handler.go # 5 handlers
│ ├── types.go
│ ├── mappers.go # 4 row→DTO mappers
│ ├── store.go
│ └── handler_test.go
└── actions/ # tag: Portfolio Corporate Actions
├── service.go # 4 methods
├── handler.go # 4 handlers
├── types.go
├── validation.go # corporateActionCreateParams, validateCorporateAction
├── mappers.go # 3 row→DTO mappers
├── store.go
├── handler_test.go
└── service_test.go
| Dependency | Why |
|---|
internal/platform/cache | cache.Cache for Get/SetJSON; key constants. |
internal/platform/database/sqlc | generated query API. |
internal/http/response | response envelope, error builders. |
internal/http/handlerutil | MustCustomerID, ParseUUIDParam, DecodeAndValidate. |
internal/http/pagination | shared pagination params + envelope. |
internal/http/middleware | global auth + rate-limit middleware. |
internal/shared/pgconv | numeric/decimal/null pointer conversions (post Step 12 of refactor plan). |
internal/shared/timezone | NPT day boundaries. |
github.com/shopspring/decimal | money math without float drift. |
github.com/jackc/pgx/v5/pgtype | nullable typed DB values. |
github.com/google/uuid | v7 ids. |
- Refactor plan:
docs/portfolio/PORTFOLIO_REFACTOR_PLAN.md — the canonical structural decisions.
- Brief:
docs/portfolio/PORTFOLIO_REFACTOR_BRIEF.md — what we're trying to achieve and why.
- Branch audit:
docs/portfolio/PORTFOLIO_BRANCH_AUDIT.md — pre-refactor inventory.
- Calculation reference (§10 fixtures): trade examples and edge cases used in
core/valuation_test.go.
| Term | Definition |
|---|
| WACC | Weighted Average Cost of Capital — over open lots only, excluding sold cost. |
| Lot | A single acquisition record: (remaining_qty, cost_per_unit, source, acquired_at). |
| Lot consumption | A pointer from a SELL trade to the open lots it consumed (with consumed qty + cost). |
Cost basis (cb) | Open-lot residual cost — what a SELL would draw down from. Distinct from "total invested". |
| Day P&L | Today's change in portfolio value, decomposed into overnight + intraday-buy + intraday-sell legs. |
| NPT | Nepal Time (UTC+05:45). Trading session 11:00–15:00 NPT, Mon–Fri. |
| CGT | Capital Gains Tax — Nepalese securities tax on realised SELL profits. |
| Receivable | T+2 settlement balance from sells not yet settled. |
| Suspended | Company status != 'A' — delisted or trading-suspended. Excluded from active summary. |