/** * Paper-trading Portfolio. * * Tracks cash, positions, and P&L as the agent executes trades. Pure in-memory * math — an Exchange (mock or real) produces Fill events; Portfolio applies * them. Persistence is handled separately in store.ts so tests don't touch disk. * * This is the execution substrate for Franklin's Trading Agent vertical — * the first place where "the AI agent with a wallet" actually makes autonomous * economic decisions and carries real P&L. No live-exchange integration here * yet; MockExchange (mock-exchange.ts) gives deterministic fills for testing, * and a real ExchangeClient adapter can be dropped in later against the same * Fill contract. */ export type Side = 'buy' | 'sell'; export interface Fill { symbol: string; side: Side; qty: number; priceUsd: number; /** Fee actually charged by the venue, USD. Required: a missing fee is a bug, not $0. */ feeUsd: number; /** Echo of the order's idempotency key when the adapter supports one. */ clientOrderId?: string; } export interface PortfolioSnapshot { cashUsd: number; realizedPnlUsd: number; positions: Position[]; } export interface Position { symbol: string; qty: number; avgPriceUsd: number; } export interface PortfolioOptions { startingCashUsd: number; } export interface MarketSnapshot { equityUsd: number; cashUsd: number; unrealizedPnlUsd: number; realizedPnlUsd: number; positions: Array; } export declare class Portfolio { cashUsd: number; realizedPnlUsd: number; private positions; constructor(opts: PortfolioOptions); /** * Validate an untrusted snapshot (a JSON file the agent's own Write tool * can edit) before it is allowed to become portfolio state. Returns the * first problem found, or `null` when the shape is sound. NaN / Infinity * anywhere here would silently disarm every RiskEngine cap, because a * non-finite projected exposure can never exceed a cap. */ static validateSnapshot(raw: unknown): string | null; getPosition(symbol: string): Position | undefined; listPositions(): Position[]; /** Serializable snapshot for persistence; paired with `restore()`. */ snapshot(): PortfolioSnapshot; /** Rehydrate state from a prior snapshot; overwrites all current fields. */ restore(snap: PortfolioSnapshot): void; applyFill(fill: Fill): void; /** * Value the portfolio against a live price table. Callers supply the marks * (e.g. from TradingSignal or a live feed) so this stays pure and testable. * Symbols with no mark are valued at avgPriceUsd (zero unrealized P&L). */ markToMarket(priceTable: Record): MarketSnapshot; }