Skip to main content

Equity Portfolio Service API

Manages equity registrations, transactions, and daily snapshots. The service fully embraces neverthrow to return Result values with structured DomainErrors.

Location: worker/services/portfolio/equity/service.ts

Dependencies

  • EquityPortfolioRepository – persistence for equities, holdings, transactions, and pricing snapshots
  • FundService – ensures funds exist and belong to the caller's organization
  • dseApi – shared HTTP client for the Dhaka Stock Exchange API
  • Shared domain error helpers – validation, conflict, forbidden, business logic errors

Public Methods

registerEquity(input: EquityRegistration): Promise<Result<{ id: string }, DomainError>> : Validates ticker/scrip code, enforces uniqueness, creates the equity, and returns the new ID. Conflicts surface as BusinessLogicError with a dedicated code.

recordTransaction(input: EquityTransaction): Promise<Result<void, DomainError>> : Verifies fund and equity existence, checks saleable quantity for sells, and records transactions in the repository.

Business Rule: All equity transactions can only be recorded on the calendar day after the last EOD date. If EOD was done for 2025-07-01, transactions can only be recorded for 2025-07-02. This validation is enforced automatically and returns a BusinessRuleViolation error with code TRANSACTION_DATE_NOT_ALLOWED if the transaction date is not allowed.

generateDailySnapshot(input: EquitySnapshot): Promise<Result<void, DomainError>> : Aggregates transactions, fetches closing prices, persists holdings, and logs when fallback pricing is used.

getHoldingsForDate(input: EquitySnapshot): Promise<Result<EquityHolding[], DomainError>> : Returns holdings for a fund on a given date.

upsertHoldingsForMigration(organizationId: string, snapshots: MigrationEquityHoldingSnapshot[]): Promise<Result<void, DomainError>> : Validates organization/fund ownership, numeric invariants, and persists migration holdings.

ensureEquities(options?: EnsureEquitiesOptions): Promise<Result<EnsureEquitiesResult, DomainError>> : Synchronizes securities from DSE, supports dry-run previews, and records per-ticker sync errors without failing the entire operation.

Error Semantics

  • createValidationError – invalid ticker/scrip code, negative quantities, improper numeric inputs
  • createNotFoundError – missing equities or funds
  • createForbiddenError – organization mismatch during migration upserts
  • createBusinessLogicError – DSE API failures, saleable quantity issues, unexpected repository errors

Usage Example

const snapshotResult = await services.portfolio.equity.generateDailySnapshot({
fundId: fund.id,
asOf: "2025-11-08",
});

await snapshotResult.match(
async () => undefined,
async (error) => {
throw mapDomainErrorToTRPC(error);
},
);

Migration Notes

  • All public methods return Result objects. Avoid _unsafeUnwrap() in callers; prefer .match() or executeResult() inside tRPC routers.
  • fetchClosingPrices remains a private helper; consumers should rely on ensureEquities and generateDailySnapshot for pricing synchronization.

Last Validated: 2025-11-10 against commit 0342a62e