mirror of
https://github.com/KhizarImran/backtestingfx.git
synced 2026-08-18 14:38:04 +00:00
feat: column-aware CSV loading and changelog
- Replaced manual CSV parsing with csv crate, columns now looked up by name - Fixed Rust example path and contract_size - Added CHANGELOG.md for release tracking Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2252b4cd8c
commit
e79070539d
@@ -0,0 +1,13 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-06-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Event-driven backtesting engine on OHLCV bar data
|
||||||
|
- Simulated broker with buy, sell, close_all, close_position
|
||||||
|
- Per-position stop loss and take profit
|
||||||
|
- Realistic FX lot sizing (0.01 / 0.10 / 1.00) with contract_size and quote_to_account conversion
|
||||||
|
- Full trade history with PnL per trade
|
||||||
|
- Stats: return, win rate, avg PnL, best/worst trade, profit factor, max drawdown
|
||||||
|
- Python API — inherit Strategy, run Backtest
|
||||||
|
- PyO3 Rust extension with Python wrapper
|
||||||
@@ -10,3 +10,4 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
pyo3 = { version = "0.28", features = ["extension-module"]}
|
pyo3 = { version = "0.28", features = ["extension-module"]}
|
||||||
|
csv = "1"
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
use backtestingfx::types::Bar;
|
use _backtestingfx::broker::Broker;
|
||||||
use backtestingfx::broker::Broker;
|
use _backtestingfx::data::load_csv;
|
||||||
use backtestingfx::strategy::Strategy;
|
use _backtestingfx::engine::Engine;
|
||||||
use backtestingfx::engine::Engine;
|
use _backtestingfx::strategy::Strategy;
|
||||||
use backtestingfx::data::load_csv;
|
use _backtestingfx::types::Bar;
|
||||||
|
|
||||||
|
|
||||||
struct BuyEveryBar;
|
struct BuyEveryBar;
|
||||||
|
|
||||||
impl Strategy for BuyEveryBar {
|
impl Strategy for BuyEveryBar {
|
||||||
fn next (&mut self, bar: &Bar, broker: &mut Broker) {
|
fn next(&mut self, bar: &Bar, broker: &mut Broker) {
|
||||||
broker.close_all(bar.close, bar.timestamp); //closes any open positions
|
broker.close_all(bar.close, bar.timestamp); //closes any open positions
|
||||||
broker.buy(bar.close, 1.0, bar.timestamp, None, None); // can be more complicated with buy, sells, close position, close all etc.
|
broker.buy(bar.close, 1.0, bar.timestamp, None, None); // can be more complicated with buy, sells, close position, close all etc.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let data = load_csv("examples/data/eurusd_lse_1h.csv");
|
let data = load_csv("data/EURUSD_1H.csv");
|
||||||
let mut engine = Engine::new(data, 10_000.0, 0.0, 0.00010);
|
let mut engine = Engine::new(data, 10_000.0, 0.0, 0.00010, 1.0, 1.0);
|
||||||
let mut strategy = BuyEveryBar;
|
let mut strategy = BuyEveryBar;
|
||||||
|
|
||||||
let stats = engine.run(&mut strategy);
|
let stats = engine.run(&mut strategy);
|
||||||
println!("{}", stats);
|
println!("{}", stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+16
-19
@@ -1,31 +1,28 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::{BufRead, BufReader};
|
|
||||||
use crate::types::Bar;
|
use crate::types::Bar;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
use csv::Reader;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
pub fn load_csv(path: &str) -> Vec<Bar> {
|
pub fn load_csv(path: &str) -> Vec<Bar> {
|
||||||
let file = File::open(path).expect("Could not open file");
|
let file = File::open(path).expect("Could not open file");
|
||||||
let reader = BufReader::new(file);
|
let mut rdr = Reader::from_reader(file);
|
||||||
|
let headers = rdr.headers().expect("could not read headers").clone();
|
||||||
|
let col = |name: &str| headers.iter().position(|h| h == name).expect(name);
|
||||||
let mut bars = Vec::new();
|
let mut bars = Vec::new();
|
||||||
|
|
||||||
for line in reader.lines().skip(1) { //skips the header row
|
for result in rdr.records() {
|
||||||
let line = line.expect("could not read line");
|
let r = result.expect("could not read record");
|
||||||
let cols: Vec<&str> = line.split(',').collect();
|
let dt = DateTime::parse_from_rfc3339(&r[0]).expect("Bad timestamp");
|
||||||
|
|
||||||
let dt = DateTime::parse_from_rfc3339(cols[0]).expect("Bad Timestamp"); // this handles the timestamp to be in 2024-01-01 00:00:00 kind of way
|
bars.push(Bar {
|
||||||
|
|
||||||
let bar = Bar {
|
|
||||||
timestamp: dt.timestamp(),
|
timestamp: dt.timestamp(),
|
||||||
open: cols[1].parse().expect("Bad open"),
|
open: r[col("open")].parse().expect("Bad open"),
|
||||||
high: cols[2].parse().expect("Bad high"),
|
high: r[col("high")].parse().expect("Bad high"),
|
||||||
low: cols[3].parse().expect("Bad low"),
|
low: r[col("low")].parse().expect("Bad low"),
|
||||||
close: cols[4].parse().expect("Bad close"),
|
close: r[col("close")].parse().expect("Bad close"),
|
||||||
volume: cols[5].parse().expect("Bad volume"),
|
volume: r[col("volume")].parse().expect("Bad volume"),
|
||||||
};
|
});
|
||||||
|
|
||||||
bars.push(bar); // pushes each bar from the csv to an element in Bar
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bars
|
bars
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user