algorembrant's picture
Upload 25 files
59da845 verified
use crate::data::parser::{Ohlcv, Tick};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum OrderType {
Buy,
Sell,
// Add pending later
}
#[derive(Debug, Clone)]
pub struct Position {
pub ticket: u64,
pub symbol: String,
pub order_type: OrderType,
pub volume: f64,
pub open_price: f64,
pub sl: Option<f64>,
pub tp: Option<f64>,
pub open_time: String,
}
#[derive(Debug, Clone)]
pub struct ClosedTrade {
pub ticket: u64,
pub symbol: String,
pub order_type: OrderType,
pub volume: f64,
pub open_price: f64,
pub close_price: f64,
pub profit: f64,
pub open_time: String,
pub close_time: String,
}
#[derive(Debug, Clone)]
pub struct BacktestConfig {
pub initial_deposit: f64,
pub leverage: f64,
pub spread_modifier: u32,
}
pub struct Backtester {
pub config: BacktestConfig,
pub balance: f64,
pub equity: f64,
pub free_margin: f64,
pub margin: f64,
pub open_positions: Vec<Position>,
pub history: Vec<ClosedTrade>,
ticket_counter: u64,
// Internal state cache for symbols
pub current_prices: HashMap<String, (f64, f64)>, // Bid, Ask
}
impl Backtester {
pub fn new(config: BacktestConfig) -> Self {
Self {
balance: config.initial_deposit,
equity: config.initial_deposit,
free_margin: config.initial_deposit,
margin: 0.0,
config,
open_positions: Vec::new(),
history: Vec::new(),
ticket_counter: 1,
current_prices: HashMap::new(),
}
}
pub fn update_tick(&mut self, symbol: &str, bid: f64, ask: f64) {
self.current_prices.insert(symbol.to_string(), (bid, ask));
self.recalculate_equity();
}
pub fn update_ohlcv(&mut self, symbol: &str, bar: &Ohlcv) {
// Simple approximation for bar execution (using Close price for equity calculation)
let simulated_bid = bar.close;
let simulated_ask = bar.close + (bar.spread as f64 * 0.00001); // Assumes generic 5-digit broker formatting
self.update_tick(symbol, simulated_bid, simulated_ask);
}
fn recalculate_equity(&mut self) {
let mut floating_profit = 0.0;
let contract_size = 100000.0; // Assume standard Forex lots for MVP
for pos in &self.open_positions {
if let Some(&(bid, ask)) = self.current_prices.get(&pos.symbol) {
if pos.order_type == OrderType::Buy {
floating_profit += (bid - pos.open_price) * contract_size * pos.volume;
} else if pos.order_type == OrderType::Sell {
floating_profit += (pos.open_price - ask) * contract_size * pos.volume;
}
}
}
self.equity = self.balance + floating_profit;
self.free_margin = self.equity - self.margin;
}
pub fn market_order(&mut self, symbol: &str, order_type: OrderType, volume: f64, sl: Option<f64>, tp: Option<f64>, time: String) -> Result<u64, String> {
let &(bid, ask) = self.current_prices.get(symbol).ok_or("No price data for symbol")?;
// Ensure Margin
let required_margin = (volume * 100000.0) / self.config.leverage;
if self.free_margin < required_margin {
return Err("Not enough free margin".to_string());
}
let price = match order_type {
OrderType::Buy => ask,
OrderType::Sell => bid,
};
let ticket = self.ticket_counter;
self.ticket_counter += 1;
let pos = Position {
ticket,
symbol: symbol.to_string(),
order_type,
volume,
open_price: price,
sl,
tp,
open_time: time,
};
self.margin += required_margin;
self.open_positions.push(pos);
self.recalculate_equity();
Ok(ticket)
}
pub fn close_position(&mut self, ticket: u64, time: String) -> Result<(), String> {
let pos_index = self.open_positions.iter().position(|p| p.ticket == ticket)
.ok_or("Position ticket not found")?;
let pos = self.open_positions.remove(pos_index);
let &(bid, ask) = self.current_prices.get(&pos.symbol).ok_or("No price data for symbol")?;
let contract_size = 100000.0;
let (close_price, profit) = match pos.order_type {
OrderType::Buy => (bid, (bid - pos.open_price) * contract_size * pos.volume),
OrderType::Sell => (ask, (pos.open_price - ask) * contract_size * pos.volume),
};
self.balance += profit;
// Free margin back
let required_margin = (pos.volume * 100000.0) / self.config.leverage;
self.margin -= required_margin;
self.history.push(ClosedTrade {
ticket: pos.ticket,
symbol: pos.symbol.clone(),
order_type: pos.order_type,
volume: pos.volume,
open_price: pos.open_price,
close_price,
profit,
open_time: pos.open_time,
close_time: time,
});
self.recalculate_equity();
Ok(())
}
}