Skip to content

feat: TWAP-based APY, active days tracking, test platform fix#32

Merged
atomantic merged 16 commits intomainfrom
dev
Feb 10, 2026
Merged

feat: TWAP-based APY, active days tracking, test platform fix#32
atomantic merged 16 commits intomainfrom
dev

Conversation

@atomantic
Copy link
Owner

@atomantic atomantic commented Feb 10, 2026

Summary

  • TWAP-based APY for trading funds: Replace snapshot-based denominator with time-weighted average position (TWAP) so multi-cycle funds reflect actual average capital deployed over time rather than cumulative or current values
  • Active days APY calculation: APY now uses active days (time with capital deployed) instead of calendar days from first entry. Idle gaps after full liquidation no longer dilute APY. start_date removed from fund config as it's redundant with first entry date
  • Test platform detection fix: isTestPlatform() changed from endsWith('test') to startsWith('test') to match cleanup logic, fixing orphaned test funds after E2E runs

Changes

Engine (packages/engine/)

  • Add TWAP denominator calculation in aggregate.ts
  • Make start_date optional in SubFundConfig (deprecated)
  • Derive start date from earliest of trades, cashFlows, and config fallback
  • getFundStartDate defensively scans for minimum date
  • Fix computeCashInterest fallback to use asOfDate instead of new Date()
  • Export new types and functions

Server (packages/server/)

  • Track cycle boundaries for active days in fund-metrics.ts
  • Sort entries chronologically at start of computeFundFinalMetrics
  • Use fractional days for TWAP/TWAB/activeDays (matches client precision)
  • Fix test platform detection across all endpoints
  • Pass cash flows to computeFundMetrics for correct cash fund TWFS
  • Fix readFund call in import.ts to use file path
  • Remove start_date from import flows

Web (packages/web/)

  • Use active days for all APY calculations in FundDetail.tsx
  • Consistent fractional day precision for TWAP and activeDays
  • Derive min/max entry dates via reduce (handles unsorted entries)
  • Remove start_date from create/edit forms and import flows

Tests & Infrastructure

  • Fix E2E test helper for accumulate mode
  • Clean up test platform detection across E2E specs
  • Move changelog to correct v0.38.x file
  • Version bump to 0.38.3

Test Plan

  • Unit tests pass (npm run test - 222 tests)
  • TypeScript type checking passes
  • E2E tests pass with corrected test platform detection
  • Test funds properly cleaned up after E2E runs
  • Client/server day precision aligned (fractional days)

github-actions bot and others added 6 commits February 9, 2026 19:12
APY is now calculated using active days (time with capital deployed)
rather than calendar days from first entry. Idle gaps after full
liquidation no longer dilute APY. start_date removed from fund config
since it's redundant with first entry date.

- Make start_date optional in SubFundConfig (deprecated)
- Engine derives start date from first trade/cashflow entry
- Server fund-metrics tracks cycle boundaries for active days
- FundDetail.tsx uses active days for all APY calculations
- Remove start_date from create/edit forms and import flows
- Fix E2E test helper computeStartInput to handle accumulate mode
Replace snapshot-based denominator fallback chain with time-weighted
average position (TWAP) so multi-cycle funds reflect actual average
capital deployed over time rather than cumulative or current values.
isTestPlatform() used endsWith('test') which only matched platforms
named exactly 'test' or ending in 'test'. Changed to startsWith('test')
to match global-setup/teardown cleanup logic, fixing orphaned test
funds and platforms after E2E runs.
Copilot AI review requested due to automatic review settings February 10, 2026 20:57
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates fund performance metrics to use time-weighted denominators (TWAP/TWAB) and “active days” (time with capital deployed), removes reliance on start_date in fund config by deriving start dates from entries, and aligns test-platform detection with E2E cleanup behavior.

Changes:

  • Replace snapshot-based trading-fund APY denominator with TWAP and update APY day-counting to use active days.
  • Deprecate/make optional start_date across engine/server/web, updating UI and import flows to derive from first entry/transaction.
  • Fix test-platform detection logic and update E2E helpers/specs accordingly; bump version to 0.38.2.

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
scripts/generate-m1test.cjs Removes start_date from generated test config.
pages/src/engine/backtest.ts Stops setting start_date in backtest scenario config.
packages/web/src/pages/FundDetail.tsx Implements active-days APY + TWAP denominator and removes config start_date dependency.
packages/web/src/components/FundCharts.tsx Derives chart start date from first entry instead of config.
packages/web/src/components/EditFundPanel.tsx Removes start_date from edit form/state and submission payload.
packages/web/src/components/EditFundConfigModal.tsx Removes start_date from config modal form/state and submission payload.
packages/web/src/components/CreateFundModal.tsx Removes start_date from create form defaults and submission payload.
packages/web/src/components/CoinbaseImportButton.tsx Makes fundStartDate optional and only renders when present.
packages/web/src/api/funds.ts Makes FundConfig.start_date optional in the web API types.
packages/server/src/utils/test-data-generator.ts Removes start_date usage from generated server-side test fund configs.
packages/server/src/utils/fund-metrics.ts Adds active-days tracking + TWAP denominator to server-computed final metrics.
packages/server/src/services/dashboard-cache.ts Aligns test-platform predicate to startsWith('test').
packages/server/src/routes/platforms.ts Removes start_date from cash-tracking enablement flow config.
packages/server/src/routes/import.ts Derives Coinbase scrape/apply dates from earliest entries/transactions instead of config start date.
packages/server/src/routes/funds.ts Aligns test-platform predicate to startsWith('test'); derives history start date from first entry.
packages/engine/test/recommendation.test.ts Removes start_date from test config.
packages/engine/test/invariants.test.ts Removes start_date from test config.
packages/engine/test/expected-equity.test.ts Keeps start_date in one test (now optional) and adjusts formatting.
packages/engine/test/config-behaviors.test.ts Removes start_date from test config.
packages/engine/test/aggregate.test.ts Removes start_date from test config.
packages/engine/src/types.ts Deprecates start_date and makes it optional for backward compatibility.
packages/engine/src/index.ts Exports getFundStartDate from the engine public API.
packages/engine/src/expected-equity.ts Uses config start_date if present, otherwise derives from first event for cash-interest accrual.
packages/engine/src/aggregate.ts Derives start date from first trade/cashflow entry and computes TWFS accordingly.
package.json Bumps version to 0.38.2.
package-lock.json Bumps lockfile package version to 0.38.2.
e2e/yearly-simulation.spec.ts Removes start_date usage and fixes expected start-input helper call for accumulate mode.
e2e/test-utils.ts Makes start_date optional and fixes computeStartInput liquidation logic for accumulate mode.
e2e/integrity-tests.spec.ts Removes start_date from generated configs and adjusts entry data for share-based expectations.
e2e/high-priority-features.spec.ts Removes start_date from test configs.
e2e/fund-configurations.spec.ts Removes start_date from test configs.
e2e/derivatives-funds.spec.ts Removes start_date from derivatives test config generator.
e2e/cash-funds.spec.ts Removes start_date from cash fund test config generator.
PLAN.md Documents completion of TWAP + active-days changes and start_date removal.
.changelogs/v0.37.x.md Adds a changelog entry describing TWAP denominator and related removals.
Comments suppressed due to low confidence (1)

packages/server/src/utils/fund-metrics.ts:72

  • computeFundFinalMetrics uses entries[0]/entries[entries.length-1] and iterates entries assuming chronological order, but it never sorts. Because the API accepts out-of-order entries, this can yield negative/incorrect day deltas (TWAB/TWAP), wrong cycle boundaries, and an incorrect daysActive/TWAP denominator. Sort a copy of fund.entries by date at the start of this function (and use that everywhere).
      const startDate = entries.length > 0 ? entries[0]!.date : new Date().toISOString().slice(0, 10)
      const endDate = lastState.date
      const daysActive = Math.max(1, Math.floor(
        (new Date(endDate).getTime() - new Date(startDate).getTime()) / (1000 * 60 * 60 * 24)
      ))

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix platforms.ts isTestPlatform to use startsWith('test') matching
  funds.ts and dashboard-cache.ts
- Sort entries chronologically in computeFundFinalMetrics to handle
  out-of-order API entries
- Fix readFund call in import.ts to pass file path instead of fund ID
- Use min/max date derivation instead of assuming entries[0] is earliest
  in funds.ts history endpoint and FundDetail.tsx
- Make getFundStartDate defensively find earliest date via linear scan
- Remove always-true daysActive > 0 guard and redundant ternaries in
  fund-metrics APY calculation
- Move changelog entries from v0.37.x to v0.38.x to match actual version
@atomantic atomantic requested a review from Copilot February 10, 2026 21:15
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

atomantic and others added 2 commits February 10, 2026 13:31
- computeFundMetrics derives startDate from earliest of trades, cashFlows,
  and config.start_date; clamps daysActive to at least 1
- Pass entriesToCashFlows to computeFundMetrics for cash funds in both
  aggregate endpoints so TWFS and daysActive are computed correctly
- Clarify activeDays logic in FundDetail.tsx with explicit boolean vars
- Fix TWAP same-day inflation: use Math.max(0, ...) instead of
  Math.max(1, ...) for daysBetween in both client and server TWAP
  accumulation so same-date entries contribute 0 elapsed days
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

atomantic and others added 2 commits February 10, 2026 13:57
- Use fractional days consistently for TWAP, TWAB, and activeDays
  calculations on both server and client (removes Math.floor from
  server day deltas to match client precision)
- Fix computeCashInterest fallback to use asOfDate instead of
  new Date() which could produce negative periods in backtests
- Update PR description to reflect current version (0.38.3)
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

packages/server/src/utils/fund-metrics.ts:73

  • In the derivatives-fund branch, daysActive is still computed using Math.floor(...), which drops fractional days. Elsewhere in this PR you switched to fractional-day precision (no flooring) to align client/server metrics. Consider using the same fractional-day calculation here (and the same clamping strategy) so derivatives APY/day counts are consistent with the rest of the system and with the client-side FundDetail calculations.
      const startDate = entries.length > 0 ? entries[0]!.date : new Date().toISOString().slice(0, 10)
      const endDate = lastState.date
      const daysActive = Math.max(1, Math.floor(
        (new Date(endDate).getTime() - new Date(startDate).getTime()) / (1000 * 60 * 60 * 24)
      ))

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@atomantic atomantic merged commit 4473a99 into main Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments