← back to krejcif__rdex

Function bodies 248 total

All specs Real LLM only Function bodies
new function · rust · L26-L39 (14 LOC)
src/backtest/engine.rs
    pub fn new(
        config: FuturesConfig,
        learning: LearningEngine,
        seed: u64,
    ) -> Self {
        Self {
            portfolio: Portfolio::new(config),
            learning,
            rng: StdRng::seed_from_u64(seed),
            trades: TradeManager::new(),
            max_seen_index: 0,
            funding_rates: HashMap::new(),
        }
    }
set_funding_rates function · rust · L42-L46 (5 LOC)
src/backtest/engine.rs
    pub fn set_funding_rates(&mut self, rates: &[FundingRate]) {
        self.funding_rates = rates.iter()
            .map(|r| (r.funding_time, r.funding_rate))
            .collect();
    }
run function · rust · L55-L195 (141 LOC)
src/backtest/engine.rs
    pub fn run(
        &mut self,
        symbol: &Symbol,
        candles: &[Candle],
        warmup: usize,
    ) -> BacktestResult {
        let lookback = 60;

        if candles.len() < warmup + lookback {
            return BacktestResult::empty();
        }

        // Verify temporal order
        for i in 1..candles.len() {
            assert!(
                candles[i].open_time >= candles[i - 1].open_time,
                "LOOK-AHEAD VIOLATION: candles not in temporal order at index {}",
                i
            );
        }

        for i in warmup..candles.len() {
            assert!(
                i >= self.max_seen_index,
                "LOOK-AHEAD VIOLATION: processing index {} after seeing {}",
                i, self.max_seen_index
            );
            self.max_seen_index = i;

            let candle = &candles[i];

            // Tick adaptive parameter engine
            self.learning.adaptive.tick_candle();

            // Update portfolio mark-to-m
execute_decision function · rust · L196-L265 (70 LOC)
src/backtest/engine.rs
    fn execute_decision(
        &mut self,
        symbol: &Symbol,
        decision: &TradingDecision,
        candle: &Candle,
        atr: f64,
    ) {
        match decision.signal {
            TradeSignal::Hold => {}
            TradeSignal::Close => {
                if self.portfolio.has_position() {
                    if let Some(record) = self.portfolio.close_position(
                        candle.close, candle.close_time, self.trades.candles_held,
                        self.trades.max_adverse, &decision.strategy_name,
                        self.trades.accumulated_funding,
                    ) {
                        self.trades.on_exit(symbol, &record, &mut self.learning);
                    }
                    self.trades.reset(&self.learning);
                }
            }
            TradeSignal::Long => {
                if self.portfolio.has_position() {
                    let pos_side = self.portfolio.position.as_ref().unwrap().side;
                 
apply_funding_fee function · rust · L268-L301 (34 LOC)
src/backtest/engine.rs
    fn apply_funding_fee(&mut self, candle: &Candle) {
        let pos = match &self.portfolio.position {
            Some(p) => p,
            None => return,
        };

        let funding_interval_ms: i64 = 8 * 3600 * 1000;
        let first_funding = (candle.open_time / funding_interval_ms) * funding_interval_ms;

        let mut funding_time = first_funding;
        while funding_time <= candle.close_time {
            if funding_time >= candle.open_time && funding_time <= candle.close_time {
                let rate = self.funding_rates.get(&funding_time)
                    .copied()
                    .unwrap_or(0.0001);

                let notional = pos.size * pos.entry_price;
                let fee = match pos.side {
                    PositionSide::Long => notional * rate,
                    PositionSide::Short => -notional * rate,
                    PositionSide::Flat => 0.0,
                };

                if fee > 0.0 {
                    self.trades.add_fund
build_result function · rust · L302-L326 (25 LOC)
src/backtest/engine.rs
    fn build_result(&self) -> BacktestResult {
        let pnls: Vec<f64> = self.portfolio.trade_log.iter().map(|t| t.pnl_pct).collect();
        let periods: Vec<usize> = self.portfolio.trade_log.iter().map(|t| t.holding_periods).collect();

        let days = if self.portfolio.equity_curve.len() > 1 {
            self.portfolio.equity_curve.len() as f64 / 96.0
        } else {
            1.0
        };

        let performance = metrics::calculate_metrics(
            &self.portfolio.equity_curve,
            &pnls,
            &periods,
            days,
        );

        BacktestResult {
            performance,
            trades: self.portfolio.trade_log.clone(),
            final_equity: self.portfolio.current_equity(),
            health: self.learning.health_report(),
        }
    }
empty function · rust · L338-L354 (17 LOC)
src/backtest/engine.rs
    pub fn empty() -> Self {
        Self {
            performance: metrics::PerformanceMetrics {
                total_return_pct: 0.0, annualized_return_pct: 0.0,
                sharpe_ratio: 0.0, sortino_ratio: 0.0,
                max_drawdown_pct: 0.0, calmar_ratio: 0.0,
                win_rate: 0.0, profit_factor: 0.0,
                total_trades: 0, winning_trades: 0, losing_trades: 0,
                avg_win_pct: 0.0, avg_loss_pct: 0.0,
                avg_holding_periods: 0.0, max_consecutive_losses: 0,
                equity_curve: vec![],
            },
            trades: vec![],
            final_equity: 0.0,
            health: std::collections::HashMap::new(),
        }
    }
Powered by Repobility — scan your code at https://repobility.com
print_summary function · rust · L355-L382 (28 LOC)
src/backtest/engine.rs
    pub fn print_summary(&self) {
        let p = &self.performance;
        let total_tx_fees: f64 = self.trades.iter().map(|t| t.fees_paid).sum();
        let total_funding: f64 = self.trades.iter().map(|t| t.funding_fees_paid).sum();
        println!("\n{}", "=".repeat(60));
        println!("  BACKTEST RESULTS");
        println!("{}", "=".repeat(60));
        println!("  Total Return:       {:>10.2}%", p.total_return_pct);
        println!("  Annualized Return:  {:>10.2}%", p.annualized_return_pct);
        println!("  Sharpe Ratio:       {:>10.2}", p.sharpe_ratio);
        println!("  Sortino Ratio:      {:>10.2}", p.sortino_ratio);
        println!("  Max Drawdown:       {:>10.2}%", p.max_drawdown_pct);
        println!("  Calmar Ratio:       {:>10.2}", p.calmar_ratio);
        println!("  Win Rate:           {:>10.2}%", p.win_rate * 100.0);
        println!("  Profit Factor:      {:>10.2}", p.profit_factor);
        println!("  Total Trades:       {:>10}", p.total_trades);
   
make_trending_candles function · rust · L389-L406 (18 LOC)
src/backtest/engine.rs
    fn make_trending_candles(n: usize, start_price: f64, trend: f64) -> Vec<Candle> {
        (0..n).map(|i| {
            let price = start_price + i as f64 * trend;
            let noise = (i as f64 * 0.1).sin() * 0.5;
            Candle {
                open_time: i as i64 * 900_000,
                open: price - 0.3 + noise,
                high: price + 1.0 + noise.abs(),
                low: price - 1.0 - noise.abs(),
                close: price + noise,
                volume: 1000.0 + (i as f64 * 10.0),
                close_time: (i as i64 + 1) * 900_000 - 1,
                quote_volume: price * 1000.0,
                trades: 500,
            }
        }).collect()
    }
test_backtest_runs_without_panic function · rust · L409-L423 (15 LOC)
src/backtest/engine.rs
    fn test_backtest_runs_without_panic() {
        let learning = LearningEngine::new(
            vec!["BTCUSDT".into()],
            LearnerConfig::default(),
        );
        let mut bt = BacktestEngine::new(
            FuturesConfig::default(),
            learning,
            42,
        );
        let candles = make_trending_candles(200, 50000.0, 10.0);
        let result = bt.run(&Symbol("BTCUSDT".into()), &candles, 60);
        assert!(result.final_equity > 0.0);
        assert!(!result.performance.equity_curve.is_empty());
    }
test_temporal_order_enforced function · rust · L426-L438 (13 LOC)
src/backtest/engine.rs
    fn test_temporal_order_enforced() {
        let learning = LearningEngine::new(
            vec!["BTCUSDT".into()],
            LearnerConfig::default(),
        );
        let mut bt = BacktestEngine::new(
            FuturesConfig::default(),
            learning,
            42,
        );
        let candles = make_trending_candles(200, 50000.0, 10.0);
        let _result = bt.run(&Symbol("BTCUSDT".into()), &candles, 60);
    }
test_unordered_candles_rejected function · rust · L442-L455 (14 LOC)
src/backtest/engine.rs
    fn test_unordered_candles_rejected() {
        let learning = LearningEngine::new(
            vec!["BTCUSDT".into()],
            LearnerConfig::default(),
        );
        let mut bt = BacktestEngine::new(
            FuturesConfig::default(),
            learning,
            42,
        );
        let mut candles = make_trending_candles(200, 50000.0, 10.0);
        candles.swap(100, 50);
        let _result = bt.run(&Symbol("BTCUSDT".into()), &candles, 60);
    }
new function · rust · L45-L56 (12 LOC)
src/backtest/portfolio.rs
    pub fn new(config: FuturesConfig) -> Self {
        let equity = config.initial_equity;
        Self {
            equity,
            position: None,
            config,
            realized_pnl: 0.0,
            trade_log: Vec::new(),
            equity_curve: vec![equity],
            peak_equity: equity,
        }
    }
open_position function · rust · L59-L97 (39 LOC)
src/backtest/portfolio.rs
    pub fn open_position(
        &mut self,
        symbol: &Symbol,
        side: PositionSide,
        price: f64,
        size_fraction: f64,
        strategy: &str,
        timestamp: i64,
    ) -> bool {
        if self.position.is_some() { return false; }
        if side == PositionSide::Flat { return false; }

        let size_fraction = size_fraction.clamp(0.05, 0.5);
        let notional = self.equity * size_fraction * self.config.leverage;
        let size = notional / price;

        // Apply slippage
        let slippage = price * self.config.slippage_bps / 10_000.0;
        let entry_price = match side {
            PositionSide::Long => price + slippage,
            PositionSide::Short => price - slippage,
            PositionSide::Flat => unreachable!(),
        };

        // Pay taker fee on entry
        let fee = notional * self.config.taker_fee;
        self.equity -= fee;

        self.position = Some(Position {
            symbol: symbol.clone(),
            side
close_position function · rust · L100-L171 (72 LOC)
src/backtest/portfolio.rs
    pub fn close_position(
        &mut self,
        price: f64,
        timestamp: i64,
        candles_held: usize,
        max_adverse: f64,
        strategy: &str,
        accumulated_funding: f64,
    ) -> Option<TradeRecord> {
        let pos = self.position.take()?;

        // Apply slippage
        let slippage = price * self.config.slippage_bps / 10_000.0;
        let exit_price = match pos.side {
            PositionSide::Long => price - slippage,
            PositionSide::Short => price + slippage,
            PositionSide::Flat => return None,
        };

        let notional = pos.size * pos.entry_price;

        // Calculate P&L
        let pnl = match pos.side {
            PositionSide::Long => pos.size * (exit_price - pos.entry_price),
            PositionSide::Short => pos.size * (pos.entry_price - exit_price),
            PositionSide::Flat => 0.0,
        };

        // Pay taker fee on exit
        let exit_notional = pos.size * exit_price;
        let exit_fee =
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
update_mark function · rust · L174-L197 (24 LOC)
src/backtest/portfolio.rs
    pub fn update_mark(&mut self, price: f64) -> f64 {
        let unrealized = if let Some(ref mut pos) = self.position {
            match pos.side {
                PositionSide::Long => pos.size * (price - pos.entry_price),
                PositionSide::Short => pos.size * (pos.entry_price - price),
                PositionSide::Flat => 0.0,
            }
        } else {
            0.0
        };

        if let Some(ref mut pos) = self.position {
            pos.unrealized_pnl = unrealized;
        }

        let current_equity = self.equity + unrealized;
        self.equity_curve.push(current_equity);

        if current_equity > self.peak_equity {
            self.peak_equity = current_equity;
        }

        current_equity
    }
check_exits function · rust · L200-L224 (25 LOC)
src/backtest/portfolio.rs
    pub fn check_exits(&self, candle: &Candle, sl: Option<f64>, tp: Option<f64>) -> Option<(f64, &str)> {
        let pos = self.position.as_ref()?;

        match pos.side {
            PositionSide::Long => {
                if let Some(sl) = sl {
                    if candle.low <= sl { return Some((sl, "stop_loss")); }
                }
                if let Some(tp) = tp {
                    if candle.high >= tp { return Some((tp, "take_profit")); }
                }
            }
            PositionSide::Short => {
                if let Some(sl) = sl {
                    if candle.high >= sl { return Some((sl, "stop_loss")); }
                }
                if let Some(tp) = tp {
                    if candle.low <= tp { return Some((tp, "take_profit")); }
                }
            }
            PositionSide::Flat => {}
        }

        None
    }
check_liquidation function · rust · L227-L243 (17 LOC)
src/backtest/portfolio.rs
    pub fn check_liquidation(&self, price: f64) -> bool {
        let pos = match &self.position {
            Some(p) => p,
            None => return false,
        };

        let notional = pos.size * pos.entry_price;
        let margin = notional / self.config.leverage;
        let unrealized = match pos.side {
            PositionSide::Long => pos.size * (price - pos.entry_price),
            PositionSide::Short => pos.size * (pos.entry_price - price),
            PositionSide::Flat => 0.0,
        };

        // Liquidation when loss exceeds margin (simplified)
        unrealized < -margin * 0.95
    }
has_position function · rust · L244-L247 (4 LOC)
src/backtest/portfolio.rs
    pub fn has_position(&self) -> bool {
        self.position.is_some()
    }
current_equity function · rust · L248-L253 (6 LOC)
src/backtest/portfolio.rs
    pub fn current_equity(&self) -> f64 {
        self.equity + self.position.as_ref()
            .map(|p| p.unrealized_pnl)
            .unwrap_or(0.0)
    }
default_portfolio function · rust · L259-L262 (4 LOC)
src/backtest/portfolio.rs
    fn default_portfolio() -> Portfolio {
        Portfolio::new(FuturesConfig::default())
    }
test_open_close_long_profitable function · rust · L265-L275 (11 LOC)
src/backtest/portfolio.rs
    fn test_open_close_long_profitable() {
        let mut port = default_portfolio();
        let sym = Symbol("BTCUSDT".into());

        port.open_position(&sym, PositionSide::Long, 50000.0, 0.1, "test", 0);
        assert!(port.has_position());

        let record = port.close_position(51000.0, 1000, 5, 0.5, "test", 0.0).unwrap();
        assert!(record.pnl > 0.0, "Long from 50k to 51k should be profitable");
        assert!(!port.has_position());
    }
test_open_close_short_profitable function · rust · L278-L285 (8 LOC)
src/backtest/portfolio.rs
    fn test_open_close_short_profitable() {
        let mut port = default_portfolio();
        let sym = Symbol("BTCUSDT".into());

        port.open_position(&sym, PositionSide::Short, 50000.0, 0.1, "test", 0);
        let record = port.close_position(49000.0, 1000, 5, 0.5, "test", 0.0).unwrap();
        assert!(record.pnl > 0.0, "Short from 50k to 49k should be profitable");
    }
Source: Repobility analyzer · https://repobility.com
test_fees_reduce_pnl function · rust · L288-L297 (10 LOC)
src/backtest/portfolio.rs
    fn test_fees_reduce_pnl() {
        let mut port = default_portfolio();
        let sym = Symbol("BTCUSDT".into());

        // Open and close at same price
        port.open_position(&sym, PositionSide::Long, 50000.0, 0.1, "test", 0);
        let record = port.close_position(50000.0, 1000, 1, 0.0, "test", 0.0).unwrap();
        // Should be slightly negative due to fees + slippage
        assert!(record.pnl < 0.0, "Round-trip at same price should be negative due to fees");
    }
test_cannot_double_open function · rust · L300-L306 (7 LOC)
src/backtest/portfolio.rs
    fn test_cannot_double_open() {
        let mut port = default_portfolio();
        let sym = Symbol("BTCUSDT".into());

        assert!(port.open_position(&sym, PositionSide::Long, 50000.0, 0.1, "test", 0));
        assert!(!port.open_position(&sym, PositionSide::Long, 50000.0, 0.1, "test", 1));
    }
test_stop_loss_long function · rust · L309-L323 (15 LOC)
src/backtest/portfolio.rs
    fn test_stop_loss_long() {
        let mut port = default_portfolio();
        let sym = Symbol("BTCUSDT".into());
        port.open_position(&sym, PositionSide::Long, 50000.0, 0.1, "test", 0);

        let candle = Candle {
            open_time: 0, open: 49500.0, high: 49600.0, low: 48000.0,
            close: 48500.0, volume: 1000.0, close_time: 0,
            quote_volume: 0.0, trades: 0,
        };

        let exit = port.check_exits(&candle, Some(49000.0), Some(55000.0));
        assert!(exit.is_some());
        assert_eq!(exit.unwrap().1, "stop_loss");
    }
test_equity_curve_tracking function · rust · L326-L331 (6 LOC)
src/backtest/portfolio.rs
    fn test_equity_curve_tracking() {
        let mut port = default_portfolio();
        port.update_mark(50000.0);
        port.update_mark(50000.0);
        assert!(port.equity_curve.len() >= 3); // initial + 2 updates
    }
test_liquidation_check function · rust · L334-L348 (15 LOC)
src/backtest/portfolio.rs
    fn test_liquidation_check() {
        let mut port = default_portfolio();
        let sym = Symbol("BTCUSDT".into());
        // Use max position size 0.5 with 3x leverage
        port.open_position(&sym, PositionSide::Long, 50000.0, 0.5, "test", 0);

        // With 3x leverage, position notional = equity * 0.5 * 3 = 15000
        // margin = 15000/3 = 5000
        // Liquidation when unrealized loss > 95% of margin = 4750
        // size = 15000 / 50000 = 0.3 BTC (adjusted by slippage)
        // Loss at price P = 0.3 * (50000 - P)
        // Need: 0.3 * (50000 - P) > 4750  =>  P < 50000 - 15833 = 34167
        assert!(!port.check_liquidation(45000.0)); // 10% drop, no liq
        assert!(port.check_liquidation(30000.0));  // 40% drop, should liquidate
    }
walk_forward_validation function · rust · L6-L67 (62 LOC)
src/backtest/validation.rs
pub fn walk_forward_validation(
    symbol: &Symbol,
    candles: &[Candle],
    config: &FuturesConfig,
    learner_config: &LearnerConfig,
    n_folds: usize,
    train_ratio: f64,
) -> ValidationResult {
    let total = candles.len();
    let fold_size = total / n_folds;
    let warmup = 60;

    if fold_size < warmup * 2 {
        return ValidationResult {
            fold_results: vec![],
            is_valid: false,
            reasons: vec!["Insufficient data for walk-forward validation".into()],
        };
    }

    let mut fold_results = Vec::new();

    for fold in 0..n_folds {
        let start = fold * fold_size;
        let end = (start + fold_size).min(total);
        let split = start + ((end - start) as f64 * train_ratio) as usize;

        if split <= start + warmup || end <= split + warmup {
            continue;
        }

        let train_data = &candles[start..split];
        let test_data = &candles[start..end]; // test includes train for indicator warmup

     
permutation_test function · rust · L70-L116 (47 LOC)
src/backtest/validation.rs
pub fn permutation_test(
    trade_pnls: &[f64],
    actual_return: f64,
    n_permutations: usize,
    seed: u64,
) -> PermutationResult {
    use rand::SeedableRng;
    use rand::seq::SliceRandom;

    if trade_pnls.is_empty() {
        return PermutationResult {
            actual_return,
            mean_random_return: 0.0,
            p_value: 1.0,
            is_significant: false,
            percentile: 50.0,
        };
    }

    let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
    let mut random_returns = Vec::with_capacity(n_permutations);

    for _ in 0..n_permutations {
        let mut shuffled = trade_pnls.to_vec();
        shuffled.shuffle(&mut rng);
        let cum_return: f64 = shuffled.iter()
            .fold(1.0, |acc, &pnl| acc * (1.0 + pnl / 100.0));
        random_returns.push((cum_return - 1.0) * 100.0);
    }

    random_returns.sort_by(|a, b| a.partial_cmp(b).unwrap());
    let mean_random = random_returns.iter().sum::<f64>() / random_returns.len() as f
overfitting_check function · rust · L119-L164 (46 LOC)
src/backtest/validation.rs
pub fn overfitting_check(
    train_return: f64,
    test_return: f64,
    train_sharpe: f64,
    test_sharpe: f64,
) -> OverfittingResult {
    let mut warnings = Vec::new();

    // Return degradation
    let return_degradation = if train_return > 0.0 {
        1.0 - test_return / train_return
    } else {
        0.0
    };

    if return_degradation > 0.5 {
        warnings.push("High return degradation (>50%) from train to test".into());
    }

    // Sharpe degradation
    let sharpe_degradation = if train_sharpe > 0.0 {
        1.0 - test_sharpe / train_sharpe
    } else {
        0.0
    };

    if sharpe_degradation > 0.5 {
        warnings.push("Sharpe ratio degrades >50% out-of-sample".into());
    }

    // Train too good to be true
    if train_sharpe > 5.0 {
        warnings.push(format!("Suspiciously high train Sharpe: {:.2}", train_sharpe));
    }

    if train_return > 100.0 {
        warnings.push(format!("Suspiciously high train return: {:.1}%", train_return));
    }
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
validate_results function · rust · L165-L214 (50 LOC)
src/backtest/validation.rs
fn validate_results(folds: &[FoldResult]) -> ValidationResult {
    if folds.is_empty() {
        return ValidationResult {
            fold_results: vec![],
            is_valid: false,
            reasons: vec!["No folds completed".into()],
        };
    }

    let mut reasons = Vec::new();

    // Check if majority of folds are profitable
    let profitable_folds = folds.iter().filter(|f| f.test_return > 0.0).count();
    if profitable_folds < folds.len() / 2 {
        reasons.push(format!(
            "Only {}/{} folds profitable out-of-sample",
            profitable_folds, folds.len()
        ));
    }

    // Check for consistent returns across folds
    let returns: Vec<f64> = folds.iter().map(|f| f.test_return).collect();
    let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;
    let variance = returns.iter().map(|r| (r - mean_return).powi(2)).sum::<f64>()
        / returns.len() as f64;
    let cv = if mean_return.abs() > 0.01 { variance.sqrt() / mean_ret
print_summary function · rust · L252-L269 (18 LOC)
src/backtest/validation.rs
    pub fn print_summary(&self) {
        println!("\n--- Walk-Forward Validation ---");
        println!("Valid: {}", self.is_valid);
        for fold in &self.fold_results {
            println!(
                "  Fold {}: train={:+.2}% test={:+.2}% sharpe={:.2} dd={:.1}% trades={} wr={:.0}%",
                fold.fold, fold.train_return, fold.test_return,
                fold.test_sharpe, fold.test_drawdown,
                fold.test_trades, fold.test_win_rate * 100.0,
            );
        }
        if !self.reasons.is_empty() {
            println!("  Warnings:");
            for r in &self.reasons {
                println!("    - {}", r);
            }
        }
    }
print_summary function · rust · L273-L280 (8 LOC)
src/backtest/validation.rs
    pub fn print_summary(&self) {
        println!("\n--- Permutation Test ---");
        println!("  Actual return:       {:+.2}%", self.actual_return);
        println!("  Mean random return:  {:+.2}%", self.mean_random_return);
        println!("  P-value:             {:.4}", self.p_value);
        println!("  Significant (p<0.05): {}", self.is_significant);
        println!("  Percentile:          {:.1}%", self.percentile);
    }
print_summary function · rust · L284-L292 (9 LOC)
src/backtest/validation.rs
    pub fn print_summary(&self) {
        println!("\n--- Overfitting Check ---");
        println!("  Return degradation:  {:.0}%", self.return_degradation * 100.0);
        println!("  Sharpe degradation:  {:.0}%", self.sharpe_degradation * 100.0);
        println!("  Likely overfit:      {}", self.likely_overfit);
        for w in &self.warnings {
            println!("  WARNING: {}", w);
        }
    }
test_permutation_test function · rust · L300-L305 (6 LOC)
src/backtest/validation.rs
    fn test_permutation_test() {
        let pnls = vec![2.0, -1.0, 3.0, -0.5, 1.5, -1.0, 2.0, -0.5, 1.0, 0.5];
        let actual_return: f64 = pnls.iter().sum();
        let result = permutation_test(&pnls, actual_return, 1000, 42);
        assert!(result.p_value >= 0.0 && result.p_value <= 1.0);
    }
test_overfitting_check_good function · rust · L308-L311 (4 LOC)
src/backtest/validation.rs
    fn test_overfitting_check_good() {
        let result = overfitting_check(10.0, 8.0, 2.0, 1.8);
        assert!(!result.likely_overfit);
    }
test_overfitting_check_bad function · rust · L314-L318 (5 LOC)
src/backtest/validation.rs
    fn test_overfitting_check_bad() {
        let result = overfitting_check(50.0, 5.0, 5.5, 0.5);
        assert!(result.likely_overfit);
        assert!(!result.warnings.is_empty());
    }
save_to_csv function · rust · L5-L29 (25 LOC)
src/data/cache.rs
pub fn save_to_csv(candles: &[Candle], path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut writer = csv::Writer::from_path(path)?;

    writer.write_record(&[
        "open_time", "open", "high", "low", "close",
        "volume", "close_time", "quote_volume", "trades",
    ])?;

    for c in candles {
        writer.write_record(&[
            c.open_time.to_string(),
            c.open.to_string(),
            c.high.to_string(),
            c.low.to_string(),
            c.close.to_string(),
            c.volume.to_string(),
            c.close_time.to_string(),
            c.quote_volume.to_string(),
            c.trades.to_string(),
        ])?;
    }

    writer.flush()?;
    Ok(())
}
Powered by Repobility — scan your code at https://repobility.com
load_from_csv function · rust · L32-L64 (33 LOC)
src/data/cache.rs
pub fn load_from_csv(path: &str) -> Result<Vec<Candle>, Box<dyn std::error::Error>> {
    if !Path::new(path).exists() {
        return Err(format!("Cache file not found: {}", path).into());
    }

    let mut reader = csv::Reader::from_path(path)?;
    let mut candles = Vec::new();

    for result in reader.records() {
        let record = result?;
        let candle = Candle {
            open_time: record[0].parse()?,
            open: record[1].parse()?,
            high: record[2].parse()?,
            low: record[3].parse()?,
            close: record[4].parse()?,
            volume: record[5].parse()?,
            close_time: record[6].parse()?,
            quote_volume: record[7].parse()?,
            trades: record[8].parse()?,
        };
        candles.push(candle);
    }

    // Verify temporal ordering
    for i in 1..candles.len() {
        if candles[i].open_time < candles[i - 1].open_time {
            return Err("Cache file has non-monotonic timestamps".into());
      
cache_path function · rust · L67-L69 (3 LOC)
src/data/cache.rs
pub fn cache_path(symbol: &str, data_dir: &str) -> String {
    format!("{}/{}_15m.csv", data_dir, symbol.to_lowercase())
}
load_or_fetch function · rust · L72-L104 (33 LOC)
src/data/cache.rs
pub async fn load_or_fetch(
    symbol: &str,
    days: i64,
    data_dir: &str,
) -> Result<Vec<Candle>, Box<dyn std::error::Error>> {
    let path = cache_path(symbol, data_dir);

    // Try cache first
    if let Ok(candles) = load_from_csv(&path) {
        if !candles.is_empty() {
            let age_ms = chrono::Utc::now().timestamp_millis()
                - candles.last().unwrap().close_time;
            let age_hours = age_ms as f64 / 3_600_000.0;

            // Use cache if less than 1 hour old
            if age_hours < 1.0 {
                println!("Using cached data for {} ({} candles, {:.1}h old)",
                    symbol, candles.len(), age_hours);
                return Ok(candles);
            }
        }
    }

    // Fetch from Binance
    let candles = super::fetcher::fetch_last_n_days(symbol, days).await?;

    // Save to cache
    std::fs::create_dir_all(data_dir)?;
    save_to_csv(&candles, &path)?;
    println!("Cached {} candles for {} at {}", candles.len()
save_funding_csv function · rust · L107-L119 (13 LOC)
src/data/cache.rs
pub fn save_funding_csv(rates: &[FundingRate], path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut writer = csv::Writer::from_path(path)?;
    writer.write_record(&["symbol", "funding_time", "funding_rate"])?;
    for r in rates {
        writer.write_record(&[
            r.symbol.clone(),
            r.funding_time.to_string(),
            r.funding_rate.to_string(),
        ])?;
    }
    writer.flush()?;
    Ok(())
}
load_funding_csv function · rust · L122-L138 (17 LOC)
src/data/cache.rs
pub fn load_funding_csv(path: &str) -> Result<Vec<FundingRate>, Box<dyn std::error::Error>> {
    if !Path::new(path).exists() {
        return Err(format!("Funding cache not found: {}", path).into());
    }
    let mut reader = csv::Reader::from_path(path)?;
    let mut rates = Vec::new();
    for result in reader.records() {
        let record = result?;
        rates.push(FundingRate {
            symbol: record[0].to_string(),
            funding_time: record[1].parse()?,
            funding_rate: record[2].parse()?,
        });
    }
    rates.sort_by_key(|r| r.funding_time);
    Ok(rates)
}
funding_cache_path function · rust · L141-L143 (3 LOC)
src/data/cache.rs
pub fn funding_cache_path(symbol: &str, data_dir: &str) -> String {
    format!("{}/{}_funding.csv", data_dir, symbol.to_lowercase())
}
load_or_fetch_funding function · rust · L146-L171 (26 LOC)
src/data/cache.rs
pub async fn load_or_fetch_funding(
    symbol: &str,
    days: i64,
    data_dir: &str,
) -> Result<Vec<FundingRate>, Box<dyn std::error::Error>> {
    let path = funding_cache_path(symbol, data_dir);

    if let Ok(rates) = load_funding_csv(&path) {
        if !rates.is_empty() {
            let age_ms = chrono::Utc::now().timestamp_millis()
                - rates.last().unwrap().funding_time;
            let age_hours = age_ms as f64 / 3_600_000.0;
            if age_hours < 1.0 {
                println!("Using cached funding for {} ({} rates, {:.1}h old)",
                    symbol, rates.len(), age_hours);
                return Ok(rates);
            }
        }
    }

    let rates = super::fetcher::fetch_funding_last_n_days(symbol, days).await?;
    std::fs::create_dir_all(data_dir)?;
    save_funding_csv(&rates, &path)?;
    println!("Cached {} funding rates for {}", rates.len(), symbol);
    Ok(rates)
}
test_csv_round_trip function · rust · L178-L201 (24 LOC)
src/data/cache.rs
    fn test_csv_round_trip() {
        let candles = vec![
            Candle {
                open_time: 1000, open: 100.0, high: 101.0, low: 99.0,
                close: 100.5, volume: 500.0, close_time: 1999,
                quote_volume: 50000.0, trades: 100,
            },
            Candle {
                open_time: 2000, open: 100.5, high: 102.0, low: 100.0,
                close: 101.0, volume: 600.0, close_time: 2999,
                quote_volume: 60000.0, trades: 120,
            },
        ];

        let dir = tempfile::tempdir().unwrap();
        let path = format!("{}/test.csv", dir.path().display());

        save_to_csv(&candles, &path).unwrap();
        let loaded = load_from_csv(&path).unwrap();

        assert_eq!(loaded.len(), 2);
        assert_eq!(loaded[0].open_time, 1000);
        assert!((loaded[1].close - 101.0).abs() < 1e-10);
    }
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
fetch_candles function · rust · L8-L72 (65 LOC)
src/data/fetcher.rs
pub async fn fetch_candles(
    symbol: &str,
    interval: &str,
    start_time: i64,
    end_time: i64,
) -> Result<Vec<Candle>, Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    let mut all_candles = Vec::new();
    let mut current_start = start_time;
    let limit = 1500; // Binance max per request

    while current_start < end_time {
        let resp = client.get(BINANCE_FUTURES_URL)
            .query(&[
                ("symbol", symbol),
                ("interval", interval),
                ("startTime", &current_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 API error {}: {}", status, body).into());
        }

        let data: Vec<Vec<serde_j
fetch_last_n_days function · rust · L75-L88 (14 LOC)
src/data/fetcher.rs
pub async fn fetch_last_n_days(
    symbol: &str,
    days: i64,
) -> Result<Vec<Candle>, Box<dyn std::error::Error>> {
    let now = Utc::now();
    let start = now - Duration::days(days);

    fetch_candles(
        symbol,
        "15m",
        start.timestamp_millis(),
        now.timestamp_millis(),
    ).await
}
fetch_multi_symbol function · rust · L91-L111 (21 LOC)
src/data/fetcher.rs
pub async fn fetch_multi_symbol(
    symbols: &[&str],
    days: i64,
) -> Result<Vec<(String, Vec<Candle>)>, Box<dyn std::error::Error>> {
    let mut results = Vec::new();

    for symbol in symbols {
        println!("Fetching {} ({} days)...", symbol, days);
        match fetch_last_n_days(symbol, days).await {
            Ok(candles) => {
                println!("  {} candles fetched", candles.len());
                results.push((symbol.to_string(), candles));
            }
            Err(e) => {
                eprintln!("  Error fetching {}: {}", symbol, e);
            }
        }
    }

    Ok(results)
}
page 1 / 5next ›