Files
worldmonitor/tests/gold-intelligence-seed.test.mjs
Elie Habib ee66b6b5c2 feat(gold): Gold Intelligence v2 — positioning depth, returns, drivers (#3034)
* feat(gold): richer Gold Intelligence panel with positioning, returns, drivers

* fix(gold): restore leveragedFunds fields and derive P/S netPct in legacy fallback

Review catch on PR #3034:

1. seed-cot.mjs stopped emitting leveragedFundsLong/Short during the v2
   refactor, which would zero out the Leveraged Funds bars in the existing
   CotPositioningPanel on the next seed run. Re-read lev_money_* from the
   TFF rows and keep the fields on the output (commodity rows don't have
   this breakdown, stay at 0).
2. get-gold-intelligence legacy fallback hardcoded producerSwap.netPct to 0,
   meaning a pre-v2 COT snapshot rendered a neutral 0% Producer/Swap bar
   on deploy until seed-cot reran. Derive netPct from dealerLong/dealerShort
   (same formula as the v2 seeder). OI share stays 0 because open_interest
   wasn't captured pre-migration; clearly documented now.

Tests: added two regression guards (leveragedFunds preserved for TFF,
commodity rows emit 0 for those fields).

* fix(gold): make enrichment layer monitored and honest about freshness

Review catch on PR #3034:

- seed-commodity-quotes now writes seed-meta:market:gold-extended via
  writeExtraKeyWithMeta on every successful run. Partial / failed fetches
  skip BOTH the data write and the meta bump, so health correctly reports
  STALE_SEED instead of masking a broken Yahoo fetch with a green check.
- Require both gold (core) AND at least one driver/silver before writing,
  so a half-successful run doesn't overwrite healthy prior data with a
  degraded payload.
- Handler no longer stamps updatedAt with new Date() when the enrichment
  key is missing. Emits empty string so the panel's freshness indicator
  shows "Updated —" with a dim dot, matching reality — enrichment is
  missing, not fresh.
- Health: goldExtended entry in STANDALONE_KEYS + SEED_META (maxStaleMin
  30, matching commodity quotes), and seed-health.js advertises the
  domain so upstream monitors pick it up.

The panel already gates session/returns/drivers sections on presence, so
legacy panels without the enrichment layer stay fully functional.
2026-04-12 22:53:32 +04:00

136 lines
5.2 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { buildInstrument, computeNextCotRelease } from '../scripts/seed-cot.mjs';
describe('seed-cot: computeNextCotRelease', () => {
it('returns report date + 3 days for a Tuesday report', () => {
// 2026-04-07 is a Tuesday; next Friday release is 2026-04-10
assert.equal(computeNextCotRelease('2026-04-07'), '2026-04-10');
});
it('handles month rollover', () => {
assert.equal(computeNextCotRelease('2026-03-31'), '2026-04-03');
});
it('returns empty for empty input', () => {
assert.equal(computeNextCotRelease(''), '');
});
it('returns empty for invalid date', () => {
assert.equal(computeNextCotRelease('not-a-date'), '');
});
});
describe('seed-cot: buildInstrument (commodity kind)', () => {
const gcTarget = { name: 'Gold', code: 'GC' };
it('computes managed money net % and OI share', () => {
const current = {
report_date_as_yyyy_mm_dd: '2026-04-07',
open_interest_all: '600000',
m_money_positions_long_all: '200000',
m_money_positions_short_all: '50000',
swap_positions_long_all: '30000',
swap__positions_short_all: '180000',
};
const inst = buildInstrument(gcTarget, current, null, 'commodity');
assert.equal(inst.code, 'GC');
assert.equal(inst.openInterest, 600000);
assert.equal(inst.nextReleaseDate, '2026-04-10');
// MM: (200000-50000)/(250000) = 60%
assert.equal(inst.managedMoney.netPct, 60);
// MM OI share: 250000/600000 = 41.67%
assert.ok(Math.abs(inst.managedMoney.oiSharePct - 41.67) < 0.05);
// Producer/Swap: (30000-180000)/(210000) ≈ -71.43%
assert.ok(Math.abs(inst.producerSwap.netPct - -71.43) < 0.05);
assert.equal(inst.managedMoney.wowNetDelta, 0);
});
it('computes WoW net delta from prior row', () => {
const current = {
report_date_as_yyyy_mm_dd: '2026-04-07',
open_interest_all: '600000',
m_money_positions_long_all: '200000',
m_money_positions_short_all: '50000',
swap_positions_long_all: '30000',
swap__positions_short_all: '180000',
};
const prior = {
report_date_as_yyyy_mm_dd: '2026-03-31',
m_money_positions_long_all: '180000',
m_money_positions_short_all: '60000',
swap_positions_long_all: '40000',
swap__positions_short_all: '170000',
};
const inst = buildInstrument(gcTarget, current, prior, 'commodity');
// Prior MM net = 180000-60000 = 120000; current = 200000-50000 = 150000; delta = +30000
assert.equal(inst.managedMoney.wowNetDelta, 30000);
// Prior P/S net = 40000-170000 = -130000; current = 30000-180000 = -150000; delta = -20000
assert.equal(inst.producerSwap.wowNetDelta, -20000);
});
it('builds financial instrument from TFF fields', () => {
const target = { name: '10-Year T-Note', code: 'ZN' };
const current = {
report_date_as_yyyy_mm_dd: '2026-04-07',
open_interest_all: '5000000',
asset_mgr_positions_long: '1500000',
asset_mgr_positions_short: '500000',
dealer_positions_long_all: '400000',
dealer_positions_short_all: '1600000',
};
const inst = buildInstrument(target, current, null, 'financial');
assert.equal(inst.managedMoney.longPositions, 1500000);
assert.equal(inst.producerSwap.longPositions, 400000);
assert.equal(inst.managedMoney.netPct, 50); // (1.5M-0.5M)/2M
});
it('preserves leveragedFunds fields for financial TFF consumers', () => {
const target = { name: '10-Year T-Note', code: 'ZN' };
const current = {
report_date_as_yyyy_mm_dd: '2026-04-07',
open_interest_all: '5000000',
asset_mgr_positions_long: '1500000',
asset_mgr_positions_short: '500000',
lev_money_positions_long: '750000',
lev_money_positions_short: '250000',
dealer_positions_long_all: '400000',
dealer_positions_short_all: '1600000',
};
const inst = buildInstrument(target, current, null, 'financial');
// Regression guard: CotPositioningPanel reads these for the Leveraged Funds bar.
assert.equal(inst.leveragedFundsLong, 750000);
assert.equal(inst.leveragedFundsShort, 250000);
});
it('commodity instruments emit leveragedFunds as 0 (no equivalent field in disaggregated report)', () => {
const current = {
report_date_as_yyyy_mm_dd: '2026-04-07',
open_interest_all: '100000',
m_money_positions_long_all: '10000',
m_money_positions_short_all: '5000',
swap_positions_long_all: '2000',
swap__positions_short_all: '8000',
};
const inst = buildInstrument(gcTarget, current, null, 'commodity');
assert.equal(inst.leveragedFundsLong, 0);
assert.equal(inst.leveragedFundsShort, 0);
});
it('preserves legacy flat fields for backward compat', () => {
const current = {
report_date_as_yyyy_mm_dd: '2026-04-07',
open_interest_all: '100000',
m_money_positions_long_all: '10000',
m_money_positions_short_all: '5000',
swap_positions_long_all: '2000',
swap__positions_short_all: '8000',
};
const inst = buildInstrument(gcTarget, current, null, 'commodity');
assert.equal(inst.assetManagerLong, 10000);
assert.equal(inst.dealerShort, 8000);
assert.equal(inst.netPct, inst.managedMoney.netPct);
});
});