feat(confluence): add confluence slot catalog, library, rack, and sentiment source (M22 slice 2)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// Investor Flow — Confluence Library (M22, slice 2)
|
||||
//
|
||||
// Redundancy modeling for the Confluence Signal Engine. Correlated slots that
|
||||
// measure the same underlying phenomenon ("golden cross" and "trend alignment"
|
||||
// both read the same moving-average structure) are grouped so a rack does not
|
||||
// over-count them as independent evidence.
|
||||
//
|
||||
// Pure data + accessors. NO I/O. ADR-0007: these are evidence-accounting rules,
|
||||
// never recommendations.
|
||||
|
||||
import { CONFLUENCE_SLOTS, type ConfluenceSlot, type SlotFamily } from './confluenceSlots.ts';
|
||||
|
||||
/**
|
||||
* A redundancy group: slots that corroborate each other because they consume
|
||||
* overlapping inputs. Within a group, the first firing slot contributes full
|
||||
* evidence and each additional firing slot contributes a discounted margin
|
||||
* (`REDUNDANCY_DISCOUNT`, applied multiplicatively) so three correlated
|
||||
* bullish slots are not counted as three independent bullish facts.
|
||||
*/
|
||||
export interface RedundancyGroup {
|
||||
family: SlotFamily;
|
||||
slots: string[];
|
||||
}
|
||||
|
||||
/** Default discount applied per additional firing slot within a group. */
|
||||
export const REDUNDANCY_DISCOUNT = 0.5;
|
||||
|
||||
/** Evidence redundancy groups keyed by the phenomenon they all measure. */
|
||||
export const REDUNDANCY_GROUPS: RedundancyGroup[] = [
|
||||
{ family: 'technical', slots: ['goldenCross', 'trendAlignment', 'pullbackToEMA21'] },
|
||||
{ family: 'technical', slots: ['timeSeriesMomentum12_1', 'relativeStrengthVsSpy', 'momentumForward'] },
|
||||
{ family: 'technical', slots: ['macdBullish', 'rsiOversold'] },
|
||||
{ family: 'technical', slots: ['macdBearish', 'rsiOverbought'] },
|
||||
{ family: 'technical', slots: ['relVolume', 'volumeProfileShelf'] },
|
||||
{ family: 'institutional', slots: ['instNetActivePositive', 'insiderInformedBuy30d', 'new13da'] },
|
||||
{ family: 'macro', slots: ['ratesRegime', 'macroRegimeUp'] },
|
||||
{ family: 'macro', slots: ['consumerSentimentLow', 'breadthThrust'] },
|
||||
{ family: 'seasonal', slots: ['seasonalFavorableMonth', 'winterHalfOn', 'electionCycleFavorableYear'] },
|
||||
{ family: 'flows', slots: ['etfFlowPositive', 'cotPositioning'] },
|
||||
];
|
||||
|
||||
/** Map slot id -> the group it belongs to (a slot may be in one group). */
|
||||
export const slotGroupIndex: ReadonlyMap<string, RedundancyGroup> = new Map(
|
||||
REDUNDANCY_GROUPS.flatMap((g) => g.slots.map((id) => [id, g] as const)),
|
||||
);
|
||||
|
||||
/** The group a slot belongs to, if any. */
|
||||
export function redundancyGroupFor(slotId: string): RedundancyGroup | undefined {
|
||||
return slotGroupIndex.get(slotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective evidence weight for a set of firing slots *within one group*.
|
||||
* First firing slot contributes 1.0; each additional contributes
|
||||
* `REDUNDANCY_DISCOUNT` of the previous marginal — so N same-group firings
|
||||
* weigh 1 + 0.5 + 0.25 + ... , never N.
|
||||
*/
|
||||
export function groupWeight(firingSlotIds: string[]): number {
|
||||
if (firingSlotIds.length === 0) return 0;
|
||||
let w = 0;
|
||||
let margin = 1;
|
||||
for (let i = 0; i < firingSlotIds.length; i++) {
|
||||
w += margin;
|
||||
margin *= REDUNDANCY_DISCOUNT;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
/** A flat description of every slot for explain-note / docs rendering. */
|
||||
export function slotCatalog(): { id: string; name: string; family: SlotFamily; body: string; explain: string }[] {
|
||||
return CONFLUENCE_SLOTS.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
family: s.family,
|
||||
body: s.body,
|
||||
explain: s.explain,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Convenience: number of slots per family. */
|
||||
export function familyCounts(): Record<SlotFamily, number> {
|
||||
const counts: Record<SlotFamily, number> = { technical: 0, institutional: 0, macro: 0, seasonal: 0, flows: 0, sentiment: 0 };
|
||||
for (const s of CONFLUENCE_SLOTS) counts[s.family] += 1;
|
||||
return counts;
|
||||
}
|
||||
|
||||
/** Re-export the slot type for consumers of the library. */
|
||||
export type { ConfluenceSlot } from './confluenceSlots.ts';
|
||||
Reference in New Issue
Block a user