Function bodies 248 total
fetch_funding_rates function · rust · L114-L165 (52 LOC)src/data/fetcher.rs
pub async fn fetch_funding_rates(
symbol: &str,
start_time: i64,
end_time: i64,
) -> Result<Vec<FundingRate>, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let mut all_rates = Vec::new();
let mut current_start = start_time;
let limit = 1000; // Binance max per request
while current_start < end_time {
let resp = client.get(BINANCE_FUNDING_URL)
.query(&[
("symbol", symbol),
("startTime", ¤t_start.to_string()),
("endTime", &end_time.to_string()),
("limit", &limit.to_string()),
])
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(format!("Binance funding API error {}: {}", status, body).into());
}
let data: Vec<serde_json::Value> = resp.json().await?;
if dafetch_funding_last_n_days function · rust · L168-L175 (8 LOC)src/data/fetcher.rs
pub async fn fetch_funding_last_n_days(
symbol: &str,
days: i64,
) -> Result<Vec<FundingRate>, Box<dyn std::error::Error>> {
let now = Utc::now();
let start = now - Duration::days(days);
fetch_funding_rates(symbol, start.timestamp_millis(), now.timestamp_millis()).await
}build_market_state function · rust · L7-L28 (22 LOC)src/domain/crypto_domain.rs
pub fn build_market_state(
symbol: &Symbol,
candles: &[Candle],
index: usize,
lookback: usize,
) -> Option<MarketState> {
if index >= candles.len() { return None; }
let start = if index >= lookback { index - lookback } else { 0 };
let history: Vec<Candle> = candles[start..=index].to_vec();
let indicators = compute_indicators(&history);
let current_candle = candles[index].clone();
Some(MarketState {
symbol: symbol.clone(),
timestamp: current_candle.close_time,
current_candle,
history,
indicators,
})
}extract_context function · rust · L31-L37 (7 LOC)src/domain/crypto_domain.rs
pub fn extract_context(state: &MarketState) -> MarketContext {
let (volatility_tier, trend_regime) = classify_regime(&state.indicators);
MarketContext {
volatility_tier,
trend_regime,
}
}make_test_candles function · rust · L42-L55 (14 LOC)src/domain/crypto_domain.rs
fn make_test_candles(n: usize) -> Vec<Candle> {
(0..n).map(|i| Candle {
open_time: i as i64 * 900_000,
open: 100.0 + (i as f64 * 0.1),
high: 101.0 + (i as f64 * 0.1),
low: 99.0 + (i as f64 * 0.1),
close: 100.5 + (i as f64 * 0.1),
volume: 1000.0,
close_time: (i + 1) as i64 * 900_000 - 1,
quote_volume: 100_000.0,
trades: 500,
}).collect()
}test_build_market_state_no_lookahead function · rust · L58-L71 (14 LOC)src/domain/crypto_domain.rs
fn test_build_market_state_no_lookahead() {
let candles = make_test_candles(100);
let state = build_market_state(
&Symbol("BTCUSDT".into()),
&candles,
50, // current index
60, // lookback
).unwrap();
// CRITICAL: state should NOT contain any candle after index 50
assert!(state.history.len() <= 51);
let max_time = state.history.iter().map(|c| c.close_time).max().unwrap();
assert!(max_time <= candles[50].close_time);
}test_build_market_state_at_start function · rust · L74-L83 (10 LOC)src/domain/crypto_domain.rs
fn test_build_market_state_at_start() {
let candles = make_test_candles(100);
let state = build_market_state(
&Symbol("BTCUSDT".into()),
&candles,
5,
60,
).unwrap();
assert_eq!(state.history.len(), 6); // indices 0..=5
}Same scanner, your repo: https://repobility.com — Repobility
test_build_market_state_out_of_bounds function · rust · L86-L94 (9 LOC)src/domain/crypto_domain.rs
fn test_build_market_state_out_of_bounds() {
let candles = make_test_candles(10);
assert!(build_market_state(
&Symbol("BTCUSDT".into()),
&candles,
15,
60,
).is_none());
}test_extract_context function · rust · L97-L109 (13 LOC)src/domain/crypto_domain.rs
fn test_extract_context() {
let candles = make_test_candles(100);
let state = build_market_state(
&Symbol("BTCUSDT".into()),
&candles,
99,
60,
).unwrap();
let ctx = extract_context(&state);
// Should produce valid context strings
assert!(!ctx.volatility_tier.is_empty());
assert!(!ctx.trend_regime.is_empty());
}compute_indicators function · rust · L6-L32 (27 LOC)src/domain/indicators.rs
pub fn compute_indicators(candles: &[Candle]) -> IndicatorSet {
if candles.len() < 50 {
return IndicatorSet::default();
}
let closes: Vec<f64> = candles.iter().map(|c| c.close).collect();
let highs: Vec<f64> = candles.iter().map(|c| c.high).collect();
let lows: Vec<f64> = candles.iter().map(|c| c.low).collect();
let volumes: Vec<f64> = candles.iter().map(|c| c.volume).collect();
let sma_20 = sma(&closes, 20);
let sma_50 = sma(&closes, 50);
let ema_12 = ema(&closes, 12);
let ema_26 = ema(&closes, 26);
let rsi_14 = rsi(&closes, 14);
let atr_14 = atr(&highs, &lows, &closes, 14);
let (macd_line, macd_signal, macd_histogram) = macd(&closes);
let (bb_upper, bb_middle, bb_lower) = bollinger_bands(&closes, 20, 2.0);
let adx_14 = adx(&highs, &lows, &closes, 14);
let volume_sma_20 = sma(&volumes, 20);
IndicatorSet {
sma_20, sma_50, ema_12, ema_26, rsi_14, atr_14,
macd_line, macd_signal, macd_histogram,sma function · rust · L33-L38 (6 LOC)src/domain/indicators.rs
fn sma(data: &[f64], period: usize) -> f64 {
if data.len() < period { return 0.0; }
let slice = &data[data.len() - period..];
slice.iter().sum::<f64>() / period as f64
}ema function · rust · L39-L48 (10 LOC)src/domain/indicators.rs
fn ema(data: &[f64], period: usize) -> f64 {
if data.len() < period { return 0.0; }
let multiplier = 2.0 / (period as f64 + 1.0);
let mut ema_val = sma(&data[..period], period);
for &val in &data[period..] {
ema_val = (val - ema_val) * multiplier + ema_val;
}
ema_val
}rsi function · rust · L49-L80 (32 LOC)src/domain/indicators.rs
fn rsi(closes: &[f64], period: usize) -> f64 {
if closes.len() < period + 1 { return 50.0; }
let mut avg_gain = 0.0;
let mut avg_loss = 0.0;
// Initial averages
for i in 1..=period {
let change = closes[i] - closes[i - 1];
if change > 0.0 { avg_gain += change; }
else { avg_loss += change.abs(); }
}
avg_gain /= period as f64;
avg_loss /= period as f64;
// Smoothed averages
for i in (period + 1)..closes.len() {
let change = closes[i] - closes[i - 1];
let (gain, loss) = if change > 0.0 {
(change, 0.0)
} else {
(0.0, change.abs())
};
avg_gain = (avg_gain * (period as f64 - 1.0) + gain) / period as f64;
avg_loss = (avg_loss * (period as f64 - 1.0) + loss) / period as f64;
}
if avg_loss < 1e-10 { return 100.0; }
let rs = avg_gain / avg_loss;
100.0 - (100.0 / (1.0 + rs))
}atr function · rust · L81-L99 (19 LOC)src/domain/indicators.rs
fn atr(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> f64 {
if closes.len() < period + 1 { return 0.0; }
let mut trs = Vec::with_capacity(closes.len() - 1);
for i in 1..closes.len() {
let tr = (highs[i] - lows[i])
.max((highs[i] - closes[i - 1]).abs())
.max((lows[i] - closes[i - 1]).abs());
trs.push(tr);
}
if trs.len() < period { return 0.0; }
let mut atr_val: f64 = trs[..period].iter().sum::<f64>() / period as f64;
for &tr in &trs[period..] {
atr_val = (atr_val * (period as f64 - 1.0) + tr) / period as f64;
}
atr_val
}macd function · rust · L100-L128 (29 LOC)src/domain/indicators.rs
fn macd(closes: &[f64]) -> (f64, f64, f64) {
if closes.len() < 35 { return (0.0, 0.0, 0.0); }
let ema12 = ema(closes, 12);
let ema26 = ema(closes, 26);
let macd_line = ema12 - ema26;
// Build MACD line series for signal
let mut macd_series = Vec::new();
let mult12 = 2.0 / 13.0;
let mult26 = 2.0 / 27.0;
let mut e12 = sma(&closes[..12], 12);
let mut e26 = sma(&closes[..26], 26);
for i in 26..closes.len() {
e12 = (closes[i] - e12) * mult12 + e12;
e26 = (closes[i] - e26) * mult26 + e26;
macd_series.push(e12 - e26);
}
let macd_signal = if macd_series.len() >= 9 {
ema(&macd_series, 9)
} else {
0.0
};
(macd_line, macd_signal, macd_line - macd_signal)
}Want this analysis on your repo? https://repobility.com/scan/
bollinger_bands function · rust · L129-L137 (9 LOC)src/domain/indicators.rs
fn bollinger_bands(closes: &[f64], period: usize, num_std: f64) -> (f64, f64, f64) {
if closes.len() < period { return (0.0, 0.0, 0.0); }
let slice = &closes[closes.len() - period..];
let mean = slice.iter().sum::<f64>() / period as f64;
let variance = slice.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / period as f64;
let std = variance.sqrt();
(mean + num_std * std, mean, mean - num_std * std)
}adx function · rust · L138-L193 (56 LOC)src/domain/indicators.rs
fn adx(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> f64 {
if closes.len() < period * 2 + 1 { return 25.0; } // neutral default
let mut plus_dm = Vec::new();
let mut minus_dm = Vec::new();
let mut tr_vals = Vec::new();
for i in 1..closes.len() {
let up = highs[i] - highs[i - 1];
let down = lows[i - 1] - lows[i];
plus_dm.push(if up > down && up > 0.0 { up } else { 0.0 });
minus_dm.push(if down > up && down > 0.0 { down } else { 0.0 });
let tr = (highs[i] - lows[i])
.max((highs[i] - closes[i - 1]).abs())
.max((lows[i] - closes[i - 1]).abs());
tr_vals.push(tr);
}
if tr_vals.len() < period { return 25.0; }
let smooth = |vals: &[f64], p: usize| -> Vec<f64> {
let mut result = vec![vals[..p].iter().sum::<f64>()];
for i in p..vals.len() {
let prev = *result.last().unwrap();
result.push(prev - prev / p as f64 + vals[i]);
classify_regime function · rust · L196-L224 (29 LOC)src/domain/indicators.rs
pub fn classify_regime(indicators: &IndicatorSet) -> (String, String) {
// Volatility tier based on BB width relative to middle
let bb_width = if indicators.bb_middle > 0.0 {
(indicators.bb_upper - indicators.bb_lower) / indicators.bb_middle
} else {
0.0
};
let volatility_tier = if bb_width < 0.02 {
"low".to_string()
} else if bb_width < 0.05 {
"medium".to_string()
} else {
"high".to_string()
};
// Trend regime based on ADX + moving average alignment
let trend_regime = if indicators.adx_14 > 25.0 {
if indicators.sma_20 > indicators.sma_50 {
"uptrend".to_string()
} else {
"downtrend".to_string()
}
} else {
"ranging".to_string()
};
(volatility_tier, trend_regime)
}make_candles function · rust · L229-L242 (14 LOC)src/domain/indicators.rs
fn make_candles(closes: &[f64]) -> Vec<Candle> {
closes.iter().enumerate().map(|(i, &c)| Candle {
open_time: i as i64 * 900_000,
open: c - 0.5,
high: c + 1.0,
low: c - 1.0,
close: c,
volume: 1000.0,
close_time: (i + 1) as i64 * 900_000 - 1,
quote_volume: c * 1000.0,
trades: 100,
}).collect()
}test_sma function · rust · L245-L249 (5 LOC)src/domain/indicators.rs
fn test_sma() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((sma(&data, 3) - 4.0).abs() < 1e-10); // (3+4+5)/3
assert!((sma(&data, 5) - 3.0).abs() < 1e-10);
}test_ema function · rust · L252-L256 (5 LOC)src/domain/indicators.rs
fn test_ema() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let result = ema(&data, 3);
assert!(result > 8.0 && result < 10.0); // EMA lags
}test_rsi_extremes function · rust · L259-L267 (9 LOC)src/domain/indicators.rs
fn test_rsi_extremes() {
// All up moves -> RSI near 100
let up: Vec<f64> = (0..30).map(|i| 100.0 + i as f64).collect();
assert!(rsi(&up, 14) > 90.0);
// All down moves -> RSI near 0
let down: Vec<f64> = (0..30).map(|i| 100.0 - i as f64).collect();
assert!(rsi(&down, 14) < 10.0);
}test_bollinger_bands function · rust · L270-L276 (7 LOC)src/domain/indicators.rs
fn test_bollinger_bands() {
let data: Vec<f64> = (0..20).map(|_| 100.0).collect();
let (upper, middle, lower) = bollinger_bands(&data, 20, 2.0);
assert!((middle - 100.0).abs() < 1e-10);
assert!((upper - 100.0).abs() < 1e-10); // zero std -> bands collapse
assert!((lower - 100.0).abs() < 1e-10);
}All rows scored by the Repobility analyzer (https://repobility.com)
test_compute_indicators_sufficient_data function · rust · L279-L287 (9 LOC)src/domain/indicators.rs
fn test_compute_indicators_sufficient_data() {
// Generate 60 candles of trending data
let closes: Vec<f64> = (0..60).map(|i| 100.0 + i as f64 * 0.5).collect();
let candles = make_candles(&closes);
let ind = compute_indicators(&candles);
assert!(ind.sma_20 > 0.0);
assert!(ind.rsi_14 > 50.0); // uptrend -> RSI > 50
assert!(ind.atr_14 > 0.0);
}test_compute_indicators_insufficient_data function · rust · L290-L294 (5 LOC)src/domain/indicators.rs
fn test_compute_indicators_insufficient_data() {
let candles = make_candles(&[100.0; 10]);
let ind = compute_indicators(&candles);
assert_eq!(ind.sma_20, 0.0); // not enough data
}test_classify_regime function · rust · L297-L311 (15 LOC)src/domain/indicators.rs
fn test_classify_regime() {
let ind = IndicatorSet {
adx_14: 30.0,
sma_20: 105.0,
sma_50: 100.0,
bb_upper: 110.0,
bb_middle: 100.0,
bb_lower: 90.0,
..Default::default()
};
let (vol, trend) = classify_regime(&ind);
assert_eq!(trend, "uptrend");
// bb_width = 20/100 = 0.2 -> high
assert_eq!(vol, "high");
}datetime function · rust · L19-L22 (4 LOC)src/domain/types.rs
pub fn datetime(&self) -> DateTime<Utc> {
DateTime::from_timestamp_millis(self.open_time)
.unwrap_or_default()
}body_ratio function · rust · L23-L28 (6 LOC)src/domain/types.rs
pub fn body_ratio(&self) -> f64 {
let range = self.high - self.low;
if range < 1e-10 { return 0.0; }
(self.close - self.open).abs() / range
}is_bullish function · rust · L29-L32 (4 LOC)src/domain/types.rs
pub fn is_bullish(&self) -> bool {
self.close > self.open
}fmt function · rust · L40-L42 (3 LOC)src/domain/types.rs
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}fmt function · rust · L127-L129 (3 LOC)src/domain/types.rs
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
default function · rust · L160-L168 (9 LOC)src/domain/types.rs
fn default() -> Self {
Self {
leverage: 3.0, // conservative 3x
maker_fee: 0.0002,
taker_fee: 0.0004,
slippage_bps: 2.0, // 2 bps slippage
initial_equity: 10_000.0,
}
}test_candle_body_ratio function · rust · L176-L185 (10 LOC)src/domain/types.rs
fn test_candle_body_ratio() {
let candle = Candle {
open_time: 0, open: 100.0, high: 110.0, low: 90.0,
close: 105.0, volume: 1000.0, close_time: 0,
quote_volume: 0.0, trades: 0,
};
let ratio = candle.body_ratio();
assert!((ratio - 0.25).abs() < 1e-10); // |105-100| / (110-90) = 5/20
assert!(candle.is_bullish());
}test_candle_bearish function · rust · L188-L195 (8 LOC)src/domain/types.rs
fn test_candle_bearish() {
let candle = Candle {
open_time: 0, open: 105.0, high: 110.0, low: 90.0,
close: 95.0, volume: 1000.0, close_time: 0,
quote_volume: 0.0, trades: 0,
};
assert!(!candle.is_bullish());
}test_futures_config_defaults function · rust · L198-L203 (6 LOC)src/domain/types.rs
fn test_futures_config_defaults() {
let cfg = FuturesConfig::default();
assert_eq!(cfg.leverage, 3.0);
assert_eq!(cfg.taker_fee, 0.0004);
assert_eq!(cfg.initial_equity, 10_000.0);
}default function · rust · L49-L63 (15 LOC)src/engine/adaptive.rs
fn default() -> Self {
Self {
total_trades: 0,
total_wins: 0,
total_candles_seen: 0,
ema_pnl: 0.0,
ema_pnl_sq: 4.0, // initial variance ~4 (std ~2%)
ema_win_duration: 15.0,
ema_loss_duration: 8.0,
ema_favorable: 4.0,
ema_adverse: 2.5,
ema_rr_ratio: 1.6, // initial: favorable/adverse = 4.0/2.5
ema_decay: 0.92,
}
}new function · rust · L67-L73 (7 LOC)src/engine/adaptive.rs
pub fn new() -> Self {
Self {
recent_outcomes: VecDeque::new(),
max_window: 100,
stats: AdaptiveStats::default(),
}
}record_trade function · rust · L76-L126 (51 LOC)src/engine/adaptive.rs
pub fn record_trade(
&mut self,
pnl_pct: f64,
duration: usize,
favorable_atr: f64,
adverse_atr: f64,
) {
let outcome = TradeOutcome {
pnl_pct,
duration,
adverse_atr,
favorable_atr,
};
self.recent_outcomes.push_back(outcome);
if self.recent_outcomes.len() > self.max_window {
self.recent_outcomes.pop_front();
}
let d = self.stats.ema_decay;
self.stats.total_trades += 1;
if pnl_pct > 0.0 {
self.stats.total_wins += 1;
}
// Update EMAs
self.stats.ema_pnl = self.stats.ema_pnl * d + pnl_pct * (1.0 - d);
self.stats.ema_pnl_sq = self.stats.ema_pnl_sq * d + (pnl_pct * pnl_pct) * (1.0 - d);
if pnl_pct > 0.0 {
self.stats.ema_win_duration =
self.stats.ema_win_duration * d + duration as f64 * (1.0 - d);
self.stats.ema_favorable =
tick_candle function · rust · L127-L130 (4 LOC)src/engine/adaptive.rs
pub fn tick_candle(&mut self) {
self.stats.total_candles_seen += 1;
}Same scanner, your repo: https://repobility.com — Repobility
pnl_std function · rust · L133-L136 (4 LOC)src/engine/adaptive.rs
fn pnl_std(&self) -> f64 {
let variance = (self.stats.ema_pnl_sq - self.stats.ema_pnl * self.stats.ema_pnl).max(0.01);
variance.sqrt()
}reward_k function · rust · L141-L143 (3 LOC)src/engine/adaptive.rs
pub fn reward_k(&self) -> f64 {
(1.0 / self.pnl_std()).clamp(0.5, 5.0)
}overall_win_rate function · rust · L146-L149 (4 LOC)src/engine/adaptive.rs
fn overall_win_rate(&self) -> f64 {
if self.stats.total_trades == 0 { return 0.5; }
self.stats.total_wins as f64 / self.stats.total_trades as f64
}cooldown function · rust · L154-L162 (9 LOC)src/engine/adaptive.rs
pub fn cooldown(&self) -> usize {
if self.stats.total_trades < 5 {
return 15;
}
let recent_wr = self.recent_win_rate().clamp(0.1, 0.9);
let cd = self.stats.ema_win_duration * (1.0 - recent_wr) / recent_wr;
cd.round().clamp(8.0, 40.0) as usize
}min_hold function · rust · L166-L174 (9 LOC)src/engine/adaptive.rs
pub fn min_hold(&self) -> usize {
if self.stats.total_trades < 10 {
return 8;
}
// Geometric mean = sqrt(win_dur * loss_dur)
// Balances the two duration signals naturally
let gm = (self.stats.ema_win_duration * self.stats.ema_loss_duration).sqrt();
gm.round().clamp(3.0, 20.0) as usize
}max_hold function · rust · L179-L187 (9 LOC)src/engine/adaptive.rs
pub fn max_hold(&self) -> usize {
if self.stats.total_trades < 10 {
return 96;
}
let wr = self.overall_win_rate().clamp(0.1, 0.9);
let loss_rate = 1.0 - wr;
let max = self.stats.ema_win_duration / (loss_rate * loss_rate);
max.round().clamp(20.0, 192.0) as usize
}trail_activation_atr function · rust · L192-L200 (9 LOC)src/engine/adaptive.rs
pub fn trail_activation_atr(&self) -> f64 {
if self.stats.total_trades < 10 {
return 1.5;
}
// activation = favorable / (1 + rr_ratio)
// With favorable=4.0 and rr=1.6: activation = 4.0/2.6 = 1.54
let activation = self.stats.ema_favorable / (1.0 + self.stats.ema_rr_ratio);
activation.clamp(0.5, 4.0)
}trail_distance_atr function · rust · L204-L211 (8 LOC)src/engine/adaptive.rs
pub fn trail_distance_atr(&self) -> f64 {
if self.stats.total_trades < 10 {
return 1.0;
}
let wr = self.overall_win_rate();
let distance = self.stats.ema_adverse * (1.0 - wr);
distance.clamp(0.3, 2.5)
}Want this analysis on your repo? https://repobility.com/scan/
breakeven_activation_atr function · rust · L215-L223 (9 LOC)src/engine/adaptive.rs
pub fn breakeven_activation_atr(&self) -> f64 {
if self.stats.total_trades < 10 {
return 0.75;
}
let total_dur = self.stats.ema_win_duration + self.stats.ema_loss_duration;
let loss_fraction = self.stats.ema_loss_duration / total_dur.max(1.0);
let be = self.stats.ema_adverse * loss_fraction;
be.clamp(0.3, 2.0)
}exploration_coeff function · rust · L228-L240 (13 LOC)src/engine/adaptive.rs
pub fn exploration_coeff(&self) -> f64 {
if self.stats.total_trades < 5 {
return 0.4;
}
let trades = self.stats.total_trades as f64;
// quality_scale = win_duration * rr_ratio (how good are our trades)
// More quality → takes more trades to reduce exploration
let quality_scale = self.stats.ema_win_duration * self.stats.ema_rr_ratio;
let wr = self.overall_win_rate();
// Start at (1-wr) — explore more when losing
let base = 1.0 - wr;
base / (1.0 + trades / quality_scale)
}kelly_fraction function · rust · L244-L259 (16 LOC)src/engine/adaptive.rs
pub fn kelly_fraction(&self) -> f64 {
if self.stats.total_trades < 15 {
return 0.35;
}
let wr = self.overall_win_rate();
let rr = self.stats.ema_rr_ratio;
// Kelly formula: f = (p * b - q) / b where p=win_rate, b=rr_ratio, q=1-p
let kelly_full = (wr * rr - (1.0 - wr)) / rr.max(0.01);
// Fraction = wr * rr / (wr * rr + 1-wr) — expected gain share of outcomes
// Purely data-derived: high EV → use more Kelly, low EV → less
let ev_plus = wr * rr;
let ev_minus = 1.0 - wr;
let fraction = ev_plus / (ev_plus + ev_minus).max(0.01);
(kelly_full * fraction).clamp(0.05, 0.50)
}