mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* 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.
136 lines
5.2 KiB
JavaScript
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);
|
|
});
|
|
});
|