Function bodies 248 total
max_position_size function · rust · L264-L275 (12 LOC)src/engine/adaptive.rs
pub fn max_position_size(&self) -> f64 {
if self.stats.total_trades < 15 {
return 0.18;
}
let rr = self.stats.ema_rr_ratio;
// rr_scale = rr / (1 + rr) — maps RR to (0, 1) range
// rr=1.0 → 0.50, rr=1.5 → 0.60, rr=2.0 → 0.67
let rr_scale = rr / (1.0 + rr);
let base = self.kelly_fraction() * rr_scale;
base.clamp(0.05, 0.30)
}confidence_score function · rust · L278-L296 (19 LOC)src/engine/adaptive.rs
fn confidence_score(&self) -> f64 {
if self.recent_outcomes.is_empty() {
return 0.0;
}
let wr = self.recent_win_rate();
let avg_pnl = self.stats.ema_pnl;
let pnl_std = self.pnl_std();
// Sharpe-like: mean / std gives consistency-adjusted confidence
let sharpe_like = (avg_pnl / pnl_std.max(0.01)).clamp(-2.0, 2.0);
let sharpe_score = (sharpe_like / 4.0 + 0.5).clamp(0.0, 1.0); // -2→0, +2→1
// Win rate score — centered on 50%
let wr_score = ((wr - 0.4) / 0.3).clamp(0.0, 1.0); // 0.4→0, 0.7→1
// Blend: Sharpe-like matters more (quality), WR for consistency
(sharpe_score * 0.6 + wr_score * 0.4).clamp(0.0, 1.0)
}recent_win_rate function · rust · L299-L305 (7 LOC)src/engine/adaptive.rs
fn recent_win_rate(&self) -> f64 {
if self.recent_outcomes.is_empty() {
return 0.5;
}
let wins = self.recent_outcomes.iter().filter(|o| o.pnl_pct > 0.0).count();
wins as f64 / self.recent_outcomes.len() as f64
}test_adaptive_params_initial function · rust · L313-L322 (10 LOC)src/engine/adaptive.rs
fn test_adaptive_params_initial() {
let params = AdaptiveParams::new();
assert_eq!(params.cooldown(), 15);
assert_eq!(params.min_hold(), 8);
assert_eq!(params.max_hold(), 96);
assert!(params.trail_activation_atr() > 0.0);
assert!(params.trail_distance_atr() > 0.0);
assert!(params.exploration_coeff() > 0.0);
assert!(params.reward_k() > 0.0);
}test_adaptive_cooldown_adjusts function · rust · L325-L347 (23 LOC)src/engine/adaptive.rs
fn test_adaptive_cooldown_adjusts() {
let mut params = AdaptiveParams::new();
// Record winning trades
for i in 0..20 {
params.tick_candle();
params.tick_candle();
params.record_trade(2.0, 10, 3.0, 1.5);
}
let winning_cooldown = params.cooldown();
let mut params2 = AdaptiveParams::new();
// Record losing trades
for i in 0..20 {
params2.tick_candle();
params2.tick_candle();
params2.record_trade(-2.0, 5, 0.5, 3.0);
}
let losing_cooldown = params2.cooldown();
assert!(losing_cooldown > winning_cooldown,
"Losing streak should produce longer cooldown: {} vs {}",
losing_cooldown, winning_cooldown);
}test_adaptive_trailing_adjusts function · rust · L350-L366 (17 LOC)src/engine/adaptive.rs
fn test_adaptive_trailing_adjusts() {
let mut params = AdaptiveParams::new();
for _ in 0..30 {
params.record_trade(3.0, 15, 6.0, 2.0);
}
let big_trail = params.trail_activation_atr();
let mut params2 = AdaptiveParams::new();
for _ in 0..30 {
params2.record_trade(1.0, 8, 2.0, 1.5);
}
let small_trail = params2.trail_activation_atr();
assert!(big_trail > small_trail,
"Big excursions should produce higher trail activation: {} vs {}",
big_trail, small_trail);
}test_confidence_increases_with_wins function · rust · L369-L376 (8 LOC)src/engine/adaptive.rs
fn test_confidence_increases_with_wins() {
let mut params = AdaptiveParams::new();
for _ in 0..30 {
params.record_trade(3.0, 10, 4.0, 1.5);
}
assert!(params.kelly_fraction() > 0.1);
assert!(params.max_position_size() > 0.05);
}About: code-quality intelligence by Repobility · https://repobility.com
test_exploration_decays function · rust · L379-L387 (9 LOC)src/engine/adaptive.rs
fn test_exploration_decays() {
let mut params = AdaptiveParams::new();
let initial = params.exploration_coeff();
for _ in 0..200 {
params.record_trade(0.5, 5, 2.0, 1.5);
}
let later = params.exploration_coeff();
assert!(later < initial, "Exploration should decay: {} vs {}", later, initial);
}test_ema_decay_adapts function · rust · L390-L398 (9 LOC)src/engine/adaptive.rs
fn test_ema_decay_adapts() {
let mut params = AdaptiveParams::new();
let initial_decay = params.stats.ema_decay;
for i in 0..100 {
params.record_trade(if i % 2 == 0 { 1.0 } else { -0.5 }, 8, 3.0, 2.0);
}
assert!(params.stats.ema_decay > initial_decay,
"EMA decay should increase with more trades");
}test_reward_k_adapts_to_volatility function · rust · L401-L415 (15 LOC)src/engine/adaptive.rs
fn test_reward_k_adapts_to_volatility() {
let mut low_vol = AdaptiveParams::new();
for _ in 0..30 {
low_vol.record_trade(0.3, 5, 1.0, 0.5); // small moves
}
let mut high_vol = AdaptiveParams::new();
for _ in 0..30 {
high_vol.record_trade(5.0, 5, 4.0, 3.0); // large moves
}
assert!(low_vol.reward_k() > high_vol.reward_k(),
"Low volatility should produce higher k: {} vs {}",
low_vol.reward_k(), high_vol.reward_k());
}default function · rust · L46-L54 (9 LOC)src/engine/learner.rs
fn default() -> Self {
Self {
decay_factor: 0.995,
plateau_window: 5,
plateau_threshold: 0.005,
transfer_after_n: 150,
min_edge: 0.02,
}
}new function · rust · L73-L75 (3 LOC)src/engine/learner.rs
fn new(initial: f64, decay: f64) -> Self {
Self { mean: initial, count: 0, decay }
}update function · rust · L76-L84 (9 LOC)src/engine/learner.rs
fn update(&mut self, val: f64) {
self.count += 1;
if self.count == 1 {
self.mean = val;
} else {
self.mean = self.mean * self.decay + val * (1.0 - self.decay);
}
}value function · rust · L85-L88 (4 LOC)src/engine/learner.rs
fn value(&self) -> f64 {
self.mean
}new function · rust · L92-L97 (6 LOC)src/engine/learner.rs
pub fn new() -> Self {
Self {
favorable: HashMap::new(),
adverse: HashMap::new(),
}
}Powered by Repobility — scan your code at https://repobility.com
record function · rust · L98-L126 (29 LOC)src/engine/learner.rs
pub fn record(&mut self, pattern_key: &str, side: &str, favorable_atr: f64, adverse_atr: f64, won: bool) {
// Record per side+pattern for side-specific learning
let key = format!("{}_{}", pattern_key, side);
self.adverse
.entry(key.clone())
.or_insert_with(|| DecayingStat::new(2.5, 0.95))
.update(adverse_atr.max(0.5));
if won {
self.favorable
.entry(key)
.or_insert_with(|| DecayingStat::new(4.0, 0.95))
.update(favorable_atr.max(1.0));
}
// Also record pattern-only (fallback for cold-start)
self.adverse
.entry(pattern_key.to_string())
.or_insert_with(|| DecayingStat::new(2.5, 0.95))
.update(adverse_atr.max(0.5));
if won {
self.favorable
.entry(pattern_key.to_string())
.or_insert_with(|| DecayingStat::new(4.0, 0.95))
.update(favget_sl_atr function · rust · L129-L135 (7 LOC)src/engine/learner.rs
pub fn get_sl_atr(&self, pattern_key: &str, side: &str) -> f64 {
let side_key = format!("{}_{}", pattern_key, side);
self.adverse.get(&side_key)
.or_else(|| self.adverse.get(pattern_key))
.map(|s| s.value().clamp(1.0, 5.0))
.unwrap_or(2.5)
}get_tp_atr function · rust · L138-L144 (7 LOC)src/engine/learner.rs
pub fn get_tp_atr(&self, pattern_key: &str, side: &str) -> f64 {
let side_key = format!("{}_{}", pattern_key, side);
self.favorable.get(&side_key)
.or_else(|| self.favorable.get(pattern_key))
.map(|s| s.value().clamp(1.5, 8.0))
.unwrap_or(4.0)
}new function · rust · L148-L173 (26 LOC)src/engine/learner.rs
pub fn new(symbols: Vec<String>, config: LearnerConfig) -> Self {
let action_names: Vec<String> = ACTIONS.iter().map(|s| s.to_string()).collect();
let thompson = ThompsonEngine::new(action_names, config.decay_factor);
let mut regret = HashMap::new();
let mut plateau = HashMap::new();
for sym in &symbols {
regret.insert(sym.clone(), RegretTracker::default());
plateau.insert(
sym.clone(),
PlateauDetector::new(config.plateau_window, config.plateau_threshold),
);
}
Self {
thompson,
discretizer: PatternDiscretizer::new(),
excursions: ExcursionTracker::new(),
regret,
plateau,
symbols,
config,
adaptive: AdaptiveParams::new(),
last_pattern: None,
}
}decide function · rust · L177-L240 (64 LOC)src/engine/learner.rs
pub fn decide(&mut self, state: &MarketState, rng: &mut impl Rng) -> TradingDecision {
let features = match MarketFeatures::extract(state) {
Some(f) => f,
None => return self.hold_decision(),
};
// Feed features to adaptive discretizer (learns boundaries)
self.discretizer.observe(&features);
let pattern = self.discretizer.discretize(&features);
let pattern_key = PatternDiscretizer::pattern_key(&pattern);
// Global pooling: all symbols share Thompson state for pattern learning.
// Crypto markets share similar microstructure across pairs.
let pool_key = "_global";
// Thompson Sampling selects action with adaptive exploration
let exploration = self.adaptive.exploration_coeff();
let action = self.thompson.select_arm_with_exploration(pool_key, &pattern, rng, exploration);
// Store pattern for outcome recording
self.last_pattern = Some(pattern.clone(hold_decision function · rust · L241-L251 (11 LOC)src/engine/learner.rs
fn hold_decision(&self) -> TradingDecision {
TradingDecision {
signal: TradeSignal::Hold,
size: 0.0,
stop_loss: None,
take_profit: None,
confidence: 0.0,
strategy_name: "hold".into(),
}
}record_outcome function · rust · L254-L270 (17 LOC)src/engine/learner.rs
pub fn record_outcome(
&mut self,
symbol: &str,
context: &MarketContext,
strategy_id: &StrategyId,
reward: f64,
) {
// Record in global pool for shared learning
self.thompson.record_outcome("_global", context, strategy_id, reward);
if let Some(regret) = self.regret.get_mut(symbol) {
regret.record(reward);
}
if let Some(plateau) = self.plateau.get_mut(symbol) {
plateau.record(reward);
}
}record_excursion function · rust · L273-L282 (10 LOC)src/engine/learner.rs
pub fn record_excursion(
&mut self,
pattern_key: &str,
side: &str,
favorable_atr: f64,
adverse_atr: f64,
won: bool,
) {
self.excursions.record(pattern_key, side, favorable_atr, adverse_atr, won);
}Repobility · MCP-ready · https://repobility.com
record_hold function · rust · L285-L292 (8 LOC)src/engine/learner.rs
pub fn record_hold(&mut self, _symbol: &str, pattern: &MarketContext, _recent_return_pct: f64) {
self.thompson.record_outcome(
"_global",
pattern,
&StrategyId("hold".into()),
0.5,
);
}maybe_transfer function · rust · L295-L329 (35 LOC)src/engine/learner.rs
pub fn maybe_transfer(&mut self) -> Vec<(String, String)> {
let mut transfers = Vec::new();
let mut symbol_evidence: Vec<(String, f64)> = self.symbols.iter()
.map(|s| {
let evidence = self.regret.get(s)
.map(|r| r.total_observations as f64)
.unwrap_or(0.0);
(s.clone(), evidence)
})
.collect();
symbol_evidence.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
if symbol_evidence.len() < 2 { return transfers; }
let source = &symbol_evidence[0].0;
if symbol_evidence[0].1 < self.config.transfer_after_n as f64 {
return transfers;
}
for (target, evidence) in &symbol_evidence[1..] {
if *evidence < self.config.transfer_after_n as f64 / 2.0 {
let should_transfer = self.plateau.get_mut(target)
.map(|pd| pd.check() != PlateauAction::Continue)
.health_report function · rust · L330-L353 (24 LOC)src/engine/learner.rs
pub fn health_report(&self) -> HashMap<String, SymbolHealth> {
let mut report: HashMap<String, SymbolHealth> = self.symbols.iter()
.map(|s| {
let regret = self.regret.get(s);
let health = SymbolHealth {
observations: regret.map(|r| r.total_observations).unwrap_or(0),
average_regret: regret.map(|r| r.average_regret()).unwrap_or(0.0),
regret_growth_rate: regret.map(|r| r.growth_rate()).unwrap_or(1.0),
is_learning: regret.map(|r| r.growth_rate() < 0.8).unwrap_or(false),
};
(s.clone(), health)
})
.collect();
report.insert("_patterns".to_string(), SymbolHealth {
observations: self.discretizer.sample_count() as u64,
average_regret: 0.0,
regret_growth_rate: 0.0,
is_learning: self.discretizer.is_warmed_up(),
});
report
}test_learning_engine_creation function · rust · L369-L376 (8 LOC)src/engine/learner.rs
fn test_learning_engine_creation() {
let engine = LearningEngine::new(
vec!["BTCUSDT".into(), "ETHUSDT".into()],
LearnerConfig::default(),
);
assert_eq!(engine.symbols.len(), 2);
assert_eq!(engine.thompson.strategy_ids.len(), 3); // long, short, hold
}test_learning_engine_decide_hold_initially function · rust · L379-L398 (20 LOC)src/engine/learner.rs
fn test_learning_engine_decide_hold_initially() {
let mut engine = LearningEngine::new(
vec!["BTCUSDT".into()],
LearnerConfig::default(),
);
let state = MarketState {
symbol: Symbol("BTCUSDT".into()),
timestamp: 0,
current_candle: Candle {
open_time: 0, open: 100.0, high: 101.0, low: 99.0,
close: 100.5, volume: 1000.0, close_time: 900_000,
quote_volume: 100_000.0, trades: 500,
},
history: vec![],
indicators: IndicatorSet::default(),
};
let mut rng = rand::thread_rng();
let decision = engine.decide(&state, &mut rng);
assert_eq!(decision.signal, TradeSignal::Hold);
}test_health_report function · rust · L401-L415 (15 LOC)src/engine/learner.rs
fn test_health_report() {
let mut engine = LearningEngine::new(
vec!["BTCUSDT".into()],
LearnerConfig::default(),
);
let ctx = MarketContext {
volatility_tier: "1111".into(),
trend_regime: "111".into(),
};
for _ in 0..20 {
engine.record_outcome("BTCUSDT", &ctx, &StrategyId("long".into()), 0.7);
}
let report = engine.health_report();
assert_eq!(report.get("BTCUSDT").unwrap().observations, 20);
}test_excursion_tracker function · rust · L418-L425 (8 LOC)src/engine/learner.rs
fn test_excursion_tracker() {
let mut tracker = ExcursionTracker::new();
for _ in 0..20 {
tracker.record("p", "long", 6.0, 1.5, true);
}
assert!(tracker.get_tp_atr("p", "long") > 4.0);
assert!(tracker.get_sl_atr("p", "long") < 3.0);
}test_excursion_defaults function · rust · L428-L432 (5 LOC)src/engine/learner.rs
fn test_excursion_defaults() {
let tracker = ExcursionTracker::new();
assert_eq!(tracker.get_sl_atr("unknown", "long"), 2.5);
assert_eq!(tracker.get_tp_atr("unknown", "long"), 4.0);
}Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
uniform function · rust · L14-L16 (3 LOC)src/engine/thompson.rs
pub fn uniform() -> Self {
Self { alpha: 1.0, beta: 1.0 }
}mean function · rust · L17-L20 (4 LOC)src/engine/thompson.rs
pub fn mean(&self) -> f64 {
self.alpha / (self.alpha + self.beta)
}variance function · rust · L21-L25 (5 LOC)src/engine/thompson.rs
pub fn variance(&self) -> f64 {
let total = self.alpha + self.beta;
(self.alpha * self.beta) / (total * total * (total + 1.0))
}sample function · rust · L26-L42 (17 LOC)src/engine/thompson.rs
pub fn sample(&self, rng: &mut impl Rng) -> f64 {
// Kumaraswamy/normal approximation for Beta sampling
let u: f64 = rng.gen_range(0.001..0.999);
if self.alpha > 1.0 && self.beta > 1.0 {
let mean = self.mean();
let std = self.variance().sqrt();
let t = if u < 0.5 {
-(-2.0 * u.ln()).sqrt()
} else {
(-2.0 * (1.0 - u).ln()).sqrt()
};
(mean + std * t).clamp(0.001, 0.999)
} else {
u.powf(1.0 / self.alpha).min(1.0 - (1.0 - u).powf(1.0 / self.beta))
}
}update function · rust · L43-L47 (5 LOC)src/engine/thompson.rs
pub fn update(&mut self, reward: f64) {
self.alpha += reward;
self.beta += 1.0 - reward;
}evidence function · rust · L48-L51 (4 LOC)src/engine/thompson.rs
pub fn evidence(&self) -> f64 {
self.alpha + self.beta - 2.0
}new function · rust · L63-L69 (7 LOC)src/engine/thompson.rs
pub fn new(decay_factor: f64) -> Self {
Self {
params: BetaParams::uniform(),
decay_factor: decay_factor.clamp(0.9, 1.0),
effective_n: 0.0,
}
}update function · rust · L70-L78 (9 LOC)src/engine/thompson.rs
pub fn update(&mut self, reward: f64) {
// Decay old evidence
self.params.alpha = 1.0 + (self.params.alpha - 1.0) * self.decay_factor;
self.params.beta = 1.0 + (self.params.beta - 1.0) * self.decay_factor;
// Add new observation
self.params.update(reward);
self.effective_n = self.effective_n * self.decay_factor + 1.0;
}About: code-quality intelligence by Repobility · https://repobility.com
effective_window function · rust · L79-L83 (5 LOC)src/engine/thompson.rs
pub fn effective_window(&self) -> f64 {
if self.decay_factor >= 1.0 { self.effective_n }
else { 1.0 / (1.0 - self.decay_factor) }
}new function · rust · L102-L115 (14 LOC)src/engine/thompson.rs
pub fn new(strategy_names: Vec<String>, decay_factor: f64) -> Self {
let strategy_ids: Vec<StrategyId> = strategy_names.into_iter()
.map(StrategyId)
.collect();
Self {
arms: HashMap::new(),
strategy_ids,
decay_factor,
exploration_coeff: 0.3,
total_decisions: 0,
visit_counts: HashMap::new(),
}
}select_arm function · rust · L119-L126 (8 LOC)src/engine/thompson.rs
pub fn select_arm(
&self,
symbol: &str,
context: &MarketContext,
rng: &mut impl Rng,
) -> StrategyId {
self.select_arm_with_exploration(symbol, context, rng, self.exploration_coeff)
}select_arm_with_exploration function · rust · L129-L151 (23 LOC)src/engine/thompson.rs
pub fn select_arm_with_exploration(
&self,
symbol: &str,
context: &MarketContext,
rng: &mut impl Rng,
exploration: f64,
) -> StrategyId {
let mut best_score = f64::NEG_INFINITY;
let mut best_arm = self.strategy_ids[0].clone();
for arm in &self.strategy_ids {
let sample = self.get_beta(symbol, context, arm).params.sample(rng);
let bonus = self.curiosity_bonus_with(symbol, context, arm, exploration);
let score = sample + bonus;
if score > best_score {
best_score = score;
best_arm = arm.clone();
}
}
best_arm
}record_outcome function · rust · L154-L177 (24 LOC)src/engine/thompson.rs
pub fn record_outcome(
&mut self,
symbol: &str,
context: &MarketContext,
arm: &StrategyId,
reward: f64,
) {
let reward = reward.clamp(0.0, 1.0);
// Update decaying beta
let beta = self.arms
.entry(symbol.to_string())
.or_default()
.entry(context.clone())
.or_default()
.entry(arm.clone())
.or_insert_with(|| DecayingBeta::new(self.decay_factor));
beta.update(reward);
// Update visit counts
let key = (symbol.to_string(), context.clone(), arm.clone());
*self.visit_counts.entry(key).or_insert(0) += 1;
self.total_decisions += 1;
}get_beta function · rust · L178-L197 (20 LOC)src/engine/thompson.rs
fn get_beta(&self, symbol: &str, context: &MarketContext, arm: &StrategyId) -> DecayingBeta {
self.arms
.get(symbol)
.and_then(|ctx| ctx.get(context))
.and_then(|arms| arms.get(arm))
.cloned()
.unwrap_or_else(|| {
// Hold-biased prior that decays with experience.
// Early: strong hold bias (alpha=5, mean=0.83).
// As total_decisions grow, hold bias weakens naturally.
let mut db = DecayingBeta::new(self.decay_factor);
if arm.0 == "hold" {
let hold_alpha = 1.0 + 4.0 / (1.0 + self.total_decisions as f64 / 100.0);
db.params.alpha = hold_alpha;
db.params.beta = 1.0;
}
db
})
}curiosity_bonus function · rust · L198-L201 (4 LOC)src/engine/thompson.rs
fn curiosity_bonus(&self, symbol: &str, context: &MarketContext, arm: &StrategyId) -> f64 {
self.curiosity_bonus_with(symbol, context, arm, self.exploration_coeff)
}curiosity_bonus_with function · rust · L202-L212 (11 LOC)src/engine/thompson.rs
fn curiosity_bonus_with(&self, symbol: &str, context: &MarketContext, arm: &StrategyId, exploration: f64) -> f64 {
if self.total_decisions < 2 { return exploration; }
let key = (symbol.to_string(), context.clone(), arm.clone());
let visits = self.visit_counts.get(&key).copied().unwrap_or(0);
if visits == 0 { return exploration * 2.0; }
exploration * ((self.total_decisions as f64).ln() / visits as f64).sqrt()
}Powered by Repobility — scan your code at https://repobility.com
get_arm_mean function · rust · L215-L218 (4 LOC)src/engine/thompson.rs
pub fn get_arm_mean(&self, symbol: &str, context: &MarketContext, arm_name: &str) -> f64 {
let arm = StrategyId(arm_name.to_string());
self.get_beta(symbol, context, &arm).params.mean()
}is_uncertain function · rust · L221-L228 (8 LOC)src/engine/thompson.rs
pub fn is_uncertain(&self, symbol: &str, context: &MarketContext, threshold: f64) -> bool {
let mut means: Vec<f64> = self.strategy_ids.iter()
.map(|arm| self.get_beta(symbol, context, arm).params.mean())
.collect();
means.sort_by(|a, b| b.partial_cmp(a).unwrap());
if means.len() < 2 { return true; }
(means[0] - means[1]) < threshold
}extract_priors function · rust · L231-L247 (17 LOC)src/engine/thompson.rs
pub fn extract_priors(&self, symbol: &str) -> HashMap<MarketContext, HashMap<StrategyId, BetaParams>> {
self.arms
.get(symbol)
.map(|contexts| {
contexts.iter()
.filter_map(|(ctx, arms)| {
let significant: HashMap<StrategyId, BetaParams> = arms.iter()
.filter(|(_, db)| db.params.evidence() > 10.0)
.map(|(arm, db)| (arm.clone(), db.params.clone()))
.collect();
if significant.is_empty() { None }
else { Some((ctx.clone(), significant)) }
})
.collect()
})
.unwrap_or_default()
}