The finance developer workbenchSearch the reference Development · 0.1.4 · unpublished

Unpublished source build: registry install examples below apply after publication. For now, build the checkout; see versions and local setup.

First useful results and first-failure recovery

These synthetic examples use the public SDK and its existing operation registry. Install @insiderfinance/totalfinance with your package manager, put the TypeScript blocks below in one journeys.mts file in order, and run pnpm exec tsx journeys.mts (add tsx as a development dependency if needed). They require no provider or key. The packed-consumer test compiles and runs these exact blocks. A prepared workspace version is not evidence that it is already on npm.

Compute from the statement you actually have

Income-only ratios accept { income }; balance-only ratios accept { balance }. Each direct request requires its period and the monetary lines its formula needs; unrelated income/balance lines and other statements are not prerequisites. Other supplied canonical fields remain validated. Complete { statements } calls remain supported and validate the complete corresponding set. EBITDA, market multiples and cross-statement/average-balance ratios still require their stated inputs. Do not invent a cash-flow table or call cumulative nine-month flows a quarter. A full-set report's period availability is the latest publication instant of its three statements; a direct statement reports its own availability. Paired reports expose both current and prior period availability; point-in-time use must respect both (including a later prior-period restatement).

import { grossMargin, currentRatio } from '@insiderfinance/totalfinance/fundamentals';
import { ohlcvFromBars, atr } from '@insiderfinance/totalfinance/technical-analysis';
import { vectorized, fees, slippage } from '@insiderfinance/totalfinance/backtest';
import { researchProtocol } from '@insiderfinance/totalfinance/risk';
import { screenUniverse } from '@insiderfinance/totalfinance/research';
import {
  createOperationRegistry,
  createMemoryArtifactStore,
  portfolioPack,
  tradePack,
  technicalAnalysisPack,
} from '@insiderfinance/totalfinance/workflows';

const period = {
  periodStartDate: '2026-01-01',
  periodEndDate: '2026-03-31',
  fiscalYear: 2026,
  fiscalQuarter: 1 as const,
  periodType: 'quarter' as const,
  availableTimestampMs: Date.UTC(2026, 3, 20),
  currency: 'USD',
  monetaryScale: 1_000_000 as const,
};
const income = { period, revenue: 200, costOfRevenue: 120 };
const balance = {
  period,
  currentAssets: 90,
  currentLiabilities: 45,
};
if (grossMargin({ income }) !== 0.4) throw new Error('Unexpected synthetic result');
if (currentRatio({ balance }) !== 2) throw new Error('Unexpected synthetic result');
if (
  grossMargin.explain({ income }).assumptions.period.availableTimestampMs !==
  period.availableTimestampMs
)
  throw new Error('Unexpected synthetic availability');

The direct family also includes operating/net margins, interest/debt-service coverage, earnings/ revenue per share, quick/cash ratios and debt/equity or debt/assets. Per-share inputs retain absolute diluted share counts and convert reported monetary scales explicitly. A missing optional numerator or zero denominator yields null with its reason in .explain().

Create, open and inspect a portfolio

Enable both the trade and portfolio packs for create → inspect: creation and the event writer live in trade, while inspection lives in portfolio and requires portfolio:read. CLI/MCP --profile full includes both. A trade-only configuration must enable portfolio before calling the returned inspection operation. Creation requires portfolio:write, a writable artifact store, durable identity, currency and explicit initial events. An empty event array creates an unfunded ledger. To fund it, supply actual explicit cash.deposit events through creation or portfolio.record_events—there is no implicit deposit.

const operations = createOperationRegistry({ packs: [portfolioPack(), tradePack()] });
const artifacts = createMemoryArtifactStore();
const createdTimestampMs = Date.UTC(2026, 3, 21);
const created = operations.run({
  id: 'totalfinance.portfolio.create',
  input: { portfolioId: 'synthetic-research', baseCurrency: 'USD', events: [], createdTimestampMs },
  artifacts,
  createdTimestampMs,
  capabilities: ['portfolio:write'],
});
const opened = operations.run({
  id: 'totalfinance.portfolio.inspect',
  input: { portfolio: created.structured['handle'], section: 'cash', limit: 20 },
  artifacts,
  createdTimestampMs,
  capabilities: ['portfolio:read'],
});
if (opened.structured['portfolioId'] !== 'synthetic-research')
  throw new Error('Unexpected synthetic result');
if (opened.structured['eventCount'] !== 0) throw new Error('Unexpected synthetic result');
if (JSON.stringify(opened.structured['rows']) !== JSON.stringify([]))
  throw new Error('Unexpected synthetic result');

Save the returned URI; the same store resolves it on the next process. For durable local use, replace createMemoryArtifactStore() with createFileArtifactStore({ directory: '/absolute/path/to/your-store' }) from @insiderfinance/totalfinance/workflows/local. CLI/MCP use --store for that same store. MCP uses --profile full --capability portfolio:write and the tools totalfinance_portfolio_create, totalfinance_portfolio_inspect, and totalfinance_portfolio_record_events. HTTP also requires its configured mutation bearer credential; a client capability does not enlarge the server's ceiling.

Inspection opens inline serialized ledgers too. section is summary (default), events, positions or cash; offset defaults to 0 and limit to 20 (maximum 100). Rows are bounded previews with container counts; total and nextOffset make continuation explicit. Use portfolio.snapshot with actual market observations to value holdings. Event writes return new content-addressed handles; prior handles retain their original meaning. Portfolio handles are opened through this operation, rather than MCP resources/read.

Project bars without losing their source

TA rows contain financial OHLCV fields. Core Bar records also identify symbol and observation time. Make that projection explicit; retain the originals and their source/revision/receipt context next to the result. No adapter infers volume, price adjustments, availability or a newer observation.

const sourceBars = [
  { symbol: 'TEST', timestampMs: 1000, open: 10, high: 12, low: 8, close: 10, volume: 2 },
  { symbol: 'TEST', timestampMs: 2000, open: 12, high: 14, low: 10, close: 12, volume: 4 },
];
const indicatorRows = ohlcvFromBars(sourceBars);
const trueRanges = atr(indicatorRows, { period: 1 });
if (JSON.stringify(trueRanges) !== JSON.stringify([4, 4]))
  throw new Error('Unexpected synthetic result');
if (sourceBars[0]!.timestampMs !== 1000) throw new Error('Unexpected synthetic result');
if ('timestampMs' in indicatorRows[0]! !== false) throw new Error('Unexpected synthetic result');

MCP technical_analysis.calculate accepts canonical sourceBars directly under bars, including symbol, timestampMs and optional vwap/adjusted; no SDK preprocessing or field stripping is needed. When source metadata is present, every row must have the canonical source fields, one symbol and strictly increasing unique timestamps. Neither the tool nor the SDK projection sorts, deduplicates, changes adjustment flags or relabels times. Bare OHLCV rows and columns under series remain supported. Source context stays with the caller, and the operation's input hash covers the original canonical input.

const technicalOperations = createOperationRegistry({ packs: [technicalAnalysisPack()] });
const sourceResult = technicalOperations.run({
  id: 'totalfinance.technical_analysis.calculate',
  input: { indicator: 'atr', bars: sourceBars, parameters: { period: 1 } },
});
if (JSON.stringify(sourceResult.structured['value']) !== JSON.stringify([4, 4]))
  throw new Error('Unexpected canonical-bar result');

Wire errors report exact total issue counts and at most ten representative indexed issues; fix the named fields and retry the original data. All rows are still validated. TA discovery distinguishes name search from category browsing: use { category: 'momentum' } with totalfinance_technical_analysis_list (SDK searchIndicators uses query for name search).

Screen only available facts with the finite predicate grammar

The recursive grammar is all: Filter[], any: Filter[], not: Filter, or a field condition:

Operators Additional fields
greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual numeric value
equals, notEquals number/string/boolean value, compatible with the declared field
between numeric from, to
in, notIn number/string values array
isPresent, isMissing none

Groups are nonempty and keys are closed. There are no gt/gte aliases or expression strings. The same grammar appears in MCP/OpenAPI discovery; declared field kinds and availability are validated by the shared SDK.

const screened = screenUniverse({
  universeId: 'synthetic-two',
  asOf: 2000,
  observations: [
    { instrumentId: 'EARLY', availableTimestampMs: 1000, fields: { momentum: 0.1 } },
    { instrumentId: 'LATER', availableTimestampMs: 3000, fields: { momentum: 0.2 } },
  ],
  fieldDefinitions: [{ fieldName: 'momentum', kind: 'numeric', unit: 'decimal return' }],
  filter: { all: [{ field: 'momentum', operator: 'greaterThan', value: 0 }] },
  missingValuePolicy: 'exclude',
  orderBy: [{ field: 'momentum', direction: 'descending' }],
});
if (JSON.stringify(screened.rows.map((row) => row.instrumentId)) !== JSON.stringify(['EARLY']))
  throw new Error('Unexpected synthetic screen membership');

Model lag and monthly sampling explicitly

Vectorized fills use closes. On a rebalance bar i, the engine samples signal[i - executionLag]. Increasing lag does not postpone a missed monthly sample to the next day. In this five-session example lag 1 first buys February 2, while lag 2 misses that sample and first buys March 2.

const dates = ['2026-01-29', '2026-01-30', '2026-02-02', '2026-02-03', '2026-03-02'];
const priceHistory = dates.map((date, index) => ({
  symbol: 'TEST',
  timestampMs: Date.parse(date),
  open: 100 + index * 10,
  high: 100 + index * 10,
  low: 100 + index * 10,
  close: 100 + index * 10,
}));
const controls = {
  data: priceHistory,
  signal: [0, 1, 1, 1, 1],
  initialCapital: 1000,
  rebalance: 'monthly' as const,
  fees: fees.bps(0),
  slippage: slippage.bps(0),
  periodsPerYear: 252,
  riskFreeRate: 0,
  maxLeverage: 1,
};
const lagOne = vectorized({ ...controls, executionLag: 1 });
const lagTwo = vectorized({ ...controls, executionLag: 2 });
if (lagOne.trades[0]!.timestampMs !== Date.parse('2026-02-02'))
  throw new Error('Unexpected synthetic result');
if (lagTwo.trades[0]!.timestampMs !== Date.parse('2026-03-02'))
  throw new Error('Unexpected synthetic result');
if (lagTwo.finalValue !== 1000) throw new Error('Unexpected synthetic result');

totalfinance_backtest_vectorized_run now accepts executionLag, rebalance, slippageBps, periodsPerYear, riskFreeRate, and maxLeverage alongside existing feeBps. They delegate to these SDK controls. Defaults remain the SDK's declared defaults; lag 0 produces a look-ahead warning. Portfolio backtests use their own next-observation execution law: do not claim cross-engine numerical equality. A one-bar lag does not prove source arrival before the next interval opened.

Count every attempted configuration and keep evaluation separate

Freeze hypotheses and the split before viewing held-out outcomes. Select using training data only; evaluate that same selected rule on the untouched holdout. Keep every attempt, including a rule that made no trades. Undefined Sharpe is not zero and must not be fabricated into trialSharpes. The count-plus-variance route below uses a separately declared synthetic null-model variance, not an invented Sharpe for the flat rule. If that variance is unavailable, retain the gap rather than claim a selection-adjusted result.

const attempts = [
  'trend: 12 training trades',
  'pullback: 8 training trades',
  'strict-filter: zero training trades',
];
const trainingReturns = [0.01, -0.01, 0.02, -0.02, 0.01, -0.01];
const heldOutReturns = [0.005, -0.005, 0.01, -0.01];
const hygiene = researchProtocol({
  returns: trainingReturns,
  outOfSampleReturns: heldOutReturns,
  trials: { trialCount: attempts.length, varianceSharpe: 0.04 },
  confidence: 0.95,
  periodsPerYear: 252,
  riskFreeRate: 0,
});
if (hygiene.trialCount !== 3) throw new Error('Unexpected synthetic result');
if (hygiene.verdict === 'significant') throw new Error('Unexpected synthetic result');

This is SDK guidance, not a new MCP research-verdict operation. See the backtesting guide for existing grid/out-of-sample operations and the research-hygiene examples. Attractive simulated returns or one short paper interval do not establish skill.

Recover orders and distinguish estimated from charged costs

trade.submit.currentOrders is the receipt-scoped journal-derived current state, including cumulative filledQuantity, remainingQuantity and terminal. receipt stays the original immutable submission receipt. Top-level fills/events are new deltas; recovery.fills/events are cumulative. After a lost response retain the original source, journal store, approved grant and idempotency key; apply recovery.events idempotently to the latest portfolio handle. See the executable recovery examples and recovery guide.

When restoring, omit instruments to reuse authoritative persisted terms, or repeat the exact original mapping. Adding explicit share/settlement defaults to an older mapping is a changed persisted identity even when it appears economically equivalent. Do not edit the journal or retry with a new key to bypass that check. Real term changes require a separate supported lifecycle.

Proposal commissions and preflight costs are estimates. maximumEstimatedCost: 0 and maximumSlippageBps: 0 now mean real zero ceilings: any positive estimate fails. Omitting a ceiling means it is not supplied; zero never disables it. The MCP paper broker still uses its disclosed simplified zero-cost policy. Estimates do not charge the ledger. Only actual normalized fill costs and corresponding cost.charge events charge execution fees. For a declared nonzero execution policy, use SDK createPaperBroker({ ..., execution }) from @insiderfinance/totalfinance/backtest/paper, with factories from /backtest/execution—the safe trade guide explains authority and recovery. This patch adds no wire paper-cost argument or cost-policy bypass.

Calendar discovery includes US_EQUITY_CALENDAR_COVERAGE: NYSE/CBOE one-off closures before 2001-01-01 are incomplete; earlier operation ranges carry calendar.outside_historical_coverage. Rules do not predict future emergency closures, and verifiedThrough: null explicitly declines to invent a certified endpoint. Session answers remain unchanged; independently verify old schedules.

Separate financial/time designs remain necessary for intraday P&L marks, availability-aware research composition, and cumulative interim/YTD statements/quarter derivation. Do not relabel source times or periods to force those unsupported compositions.