diff --git a/analysts/__tests__/froggy.enrichment_adapter.test.ts b/analysts/__tests__/froggy.enrichment_adapter.test.ts index d98ab2b..87884b0 100644 --- a/analysts/__tests__/froggy.enrichment_adapter.test.ts +++ b/analysts/__tests__/froggy.enrichment_adapter.test.ts @@ -4,6 +4,7 @@ import { type FroggyTrendPullbackScore } from "../froggy.trend_pullback_v1"; import { + BROKE_EMA_WITH_BODY_UNIMPLEMENTED_STUB, buildFroggyTrendPullbackInputFromEnriched, type FroggyEnrichedView } from "../froggy.enrichment_adapter"; @@ -160,4 +161,16 @@ describe("froggy.enrichment_adapter", () => { expect(input.atrRegime).toBe("normal"); }); }); + + it("uses the declared brokeEmaWithBody stub when technical omits the field (D5 zero-movement)", () => { + expect(BROKE_EMA_WITH_BODY_UNIMPLEMENTED_STUB).toBe(false); + const input = buildFroggyTrendPullbackInputFromEnriched({ + signalId: "stub-broke", + symbol: "BTC", + market: "crypto", + timeframe: "1h", + technical: { emaDistancePct: 0.5, isInValueSweetSpot: true } + }); + expect(input.brokeEmaWithBody).toBe(BROKE_EMA_WITH_BODY_UNIMPLEMENTED_STUB); + }); }); diff --git a/analysts/__tests__/froggy.trend_pullback_v1.test.ts b/analysts/__tests__/froggy.trend_pullback_v1.test.ts index d9d0b44..4253644 100644 --- a/analysts/__tests__/froggy.trend_pullback_v1.test.ts +++ b/analysts/__tests__/froggy.trend_pullback_v1.test.ts @@ -76,6 +76,11 @@ describe("Froggy trend_pullback_v1 analyst mapping", () => { expect(result.analystScore.direction).toBe("long"); // baseGoodInput has long bias }); + it("declares conviction as an alias of uwrScore (D5 zero-movement)", () => { + const result = scoreFroggyTrendPullback(baseGoodInput); + expect(result.analystScore.conviction).toBe(result.analystScore.uwrScore); + }); + it("emits AnalystScoreTemplate with enriched view context", () => { const enrichedView: FroggyEnrichedView = { signalId: "test-signal-123", diff --git a/analysts/froggy.enrichment_adapter.ts b/analysts/froggy.enrichment_adapter.ts index 5d7bdbe..901f381 100644 --- a/analysts/froggy.enrichment_adapter.ts +++ b/analysts/froggy.enrichment_adapter.ts @@ -38,6 +38,16 @@ export interface FroggyAiMlV1 { notes?: string | null; } +/** + * Declared stub for `brokeEmaWithBody` until a modelling filing implements a + * candle-derived producer (D5 / D5-GOV zero-movement option). + * + * Live reactor `viewTechnical` pins this exact value. It is **not** a silent + * missing-data default: it is the explicit, unimplemented-input law. Changing + * it, or wiring a real producer, is a score-moving Tier-F act. + */ +export const BROKE_EMA_WITH_BODY_UNIMPLEMENTED_STUB = false as const; + export interface FroggyEnrichedView { signalId: string; symbol: string; @@ -192,7 +202,10 @@ export function buildFroggyTrendPullbackInputFromEnriched( const distanceFromDailyEmaPct = technical.emaDistancePct ?? 0; const pulledBackIntoSweetSpot = technical.isInValueSweetSpot ?? false; - const brokeEmaWithBody = technical.brokeEmaWithBody ?? false; + // Explicit stub law (not a silent default): absent/undefined → declared + // unimplemented stub. A future producer must land via a score-moving filing. + const brokeEmaWithBody = + technical.brokeEmaWithBody ?? BROKE_EMA_WITH_BODY_UNIMPLEMENTED_STUB; const triggerPatternQuality = pattern.patternConfidence != null diff --git a/analysts/froggy.trend_pullback_v1.ts b/analysts/froggy.trend_pullback_v1.ts index 1167925..88d866d 100644 --- a/analysts/froggy.trend_pullback_v1.ts +++ b/analysts/froggy.trend_pullback_v1.ts @@ -182,8 +182,10 @@ function buildAnalystScoreTemplate( input.atrRegime === "normal" ? "medium" : input.atrRegime === "high" ? "high" : "extreme"; - // Derive conviction from UWR score (simple mapping: uwrScore is already 0-1) - const conviction = uwrScore; + // D5 / D5-GOV zero-movement: conviction is a *declared alias* of uwrScore, + // not an independently computed metric. Emitting both names is intentional + // record shape; do not treat them as separable quality numbers. + const conviction: number = uwrScore; // Derive direction from bias const direction: "long" | "short" | "neutral" | "unknown" = diff --git a/src/analyst/AnalystScoreTemplate.ts b/src/analyst/AnalystScoreTemplate.ts index 29b1066..1178f2a 100644 --- a/src/analyst/AnalystScoreTemplate.ts +++ b/src/analyst/AnalystScoreTemplate.ts @@ -72,7 +72,12 @@ export interface AnalystScoreTemplate { direction: "long" | "short" | "neutral" | "unknown"; /** Risk bucket: low, medium, high, extreme */ riskBucket: "low" | "medium" | "high" | "extreme"; - /** Conviction level (0-1 scale, where 0 = no conviction, 1 = maximum conviction) */ + /** + * Conviction on [0,1]. For froggy `trend_pullback_v1` this is a **declared + * alias of `uwrScore`** (same number under two names) — not an independent + * metric. Do not combine with `uwrScore` in a weighted sum without an + * explicit modelling filing; that would double-count UWR. + */ conviction: number; // ========== Greeks (Optional: mostly for derivatives) ==========