///|
pub(all) enum ExperienceCaseKind {
SuccessCase
FailureCase
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ExperienceMemoryEntry {
id : String
book_path : String
title : String
cycle_position : @domain.CyclePosition
direction : @domain.Direction
patterns : Array[@domain.PatternTag]
timestamp_ms : Int64
case_kind : ExperienceCaseKind
summary : String
lesson : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ExperienceMemorySelection {
cycle_position : @domain.CyclePosition
direction : @domain.Direction
requested_patterns : Array[@domain.PatternTag]
entries : Array[ExperienceMemoryEntry]
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn experience_memory_entry(
id : String,
book_path : String,
title : String,
cycle_position : @domain.CyclePosition,
direction : @domain.Direction,
patterns : Array[@domain.PatternTag],
timestamp_ms : Int64,
case_kind : ExperienceCaseKind,
summary : String,
lesson : String,
) -> ExperienceMemoryEntry {
{
id,
book_path,
title,
cycle_position,
direction,
patterns,
timestamp_ms,
case_kind,
summary,
lesson,
}
}
///|
pub fn default_experience_memory_index() -> Array[ExperienceMemoryEntry] {
[
experience_memory_entry(
"memory.tight-channel.always-in-h2.success",
"wiki/imported-experience/tight_channel/success/always-in-h2.md",
"Tight channel H2 continuation",
TightChannel,
Bullish,
[AlwaysIn, H2],
1780000000000L,
SuccessCase,
"bull tight channel continuation where H2 worked only after strong close and shallow pullback",
"prefer wait or continuation only when follow-through stays strong; do not fade always-in pressure early",
),
experience_memory_entry(
"memory.tight-channel.middle-range.failure",
"wiki/imported-experience/tight_channel/failure/middle-range-fade.md",
"Tight channel middle-range fade failure",
TightChannel,
Bearish,
[AlwaysIn, MiddleRange],
1779900000000L,
FailureCase,
"countertrend fade failed because always-in pressure and EMA separation stayed intact",
"require clear trend break before treating a channel as a reversal setup",
),
experience_memory_entry(
"memory.trading-range.no-trade.success",
"wiki/imported-experience/trading_range/success/no-trade-overlap.md",
"Trading range overlap no-trade",
TradingRange,
Neutral,
[Barbwire, Overlap, MiddleRange],
1779800000000L,
SuccessCase,
"range-middle overlap produced poor equation and no durable edge",
"default to no-order in range middle until price reaches an edge or breakout test",
),
experience_memory_entry(
"memory.normal-channel.breakout-pullback.success",
"wiki/imported-experience/normal_channel/success/breakout-pullback.md",
"Normal channel breakout pullback",
NormalChannel,
Bullish,
[BreakoutPullback, MeasuredMove],
1779700000000L,
SuccessCase,
"breakout pullback aligned with measured-move target and held above prior resistance",
"use measured move as target context, not as permission to chase weak pullbacks",
),
experience_memory_entry(
"memory.extreme-range.failed-breakout.failure",
"wiki/imported-experience/extreme_range/failure/failed-breakout.md",
"Extreme range failed breakout",
ExtremeRange,
Neutral,
[BreakoutFailure, FailedSignal],
1779600000000L,
FailureCase,
"late breakout failed back into the range after trapped entry signal",
"treat failed breakouts near range extremes as review-required unless follow-through is immediate",
),
]
}
///|
fn overlap_score(
entry : ExperienceMemoryEntry,
patterns : Array[@domain.PatternTag],
) -> Int {
patterns.fold(init=0, fn(score, pattern) {
if entry.patterns.contains(pattern) {
score + 1
} else {
score
}
})
}
///|
fn memory_score(
entry : ExperienceMemoryEntry,
diagnosis : @domain.Stage1Diagnosis,
) -> Int {
let direction_score = if entry.direction == diagnosis.direction {
2
} else {
0
}
direction_score + overlap_score(entry, diagnosis.patterns)
}
///|
fn better_memory(
candidate : ExperienceMemoryEntry,
existing : ExperienceMemoryEntry,
diagnosis : @domain.Stage1Diagnosis,
) -> Bool {
let candidate_score = memory_score(candidate, diagnosis)
let existing_score = memory_score(existing, diagnosis)
candidate_score > existing_score ||
(
candidate_score == existing_score &&
candidate.timestamp_ms > existing.timestamp_ms
)
}
///|
fn insert_ranked_memory(
ranked : Array[ExperienceMemoryEntry],
candidate : ExperienceMemoryEntry,
diagnosis : @domain.Stage1Diagnosis,
) -> Array[ExperienceMemoryEntry] {
let next : Array[ExperienceMemoryEntry] = []
let mut inserted = false
for existing in ranked {
if !inserted && better_memory(candidate, existing, diagnosis) {
next.push(candidate)
inserted = true
}
next.push(existing)
}
if !inserted {
next.push(candidate)
}
next
}
///|
fn cap_entries(
entries : Array[ExperienceMemoryEntry],
max_entries : Int,
) -> Array[ExperienceMemoryEntry] {
let limit = if max_entries < 0 {
0
} else if max_entries > entries.length() {
entries.length()
} else {
max_entries
}
let capped : Array[ExperienceMemoryEntry] = []
for index in 0.. ExperienceMemorySelection {
let mut ranked : Array[ExperienceMemoryEntry] = []
for entry in index {
if entry.cycle_position == diagnosis.cycle_position {
ranked = insert_ranked_memory(ranked, entry, diagnosis)
}
}
let entries = cap_entries(ranked, max_entries)
{
cycle_position: diagnosis.cycle_position,
direction: diagnosis.direction,
requested_patterns: diagnosis.patterns,
entries,
summary: if entries.is_empty() {
"selected 0 experience memory entries"
} else if entries.length() == 1 {
"selected 1 experience memory entry for Stage 2"
} else {
"selected \{entries.length()} experience memory entries for Stage 2"
},
}
}