work on new engine

This commit is contained in:
Boki 2025-07-04 11:24:27 -04:00
parent 44476da13f
commit a1e5a21847
126 changed files with 3425 additions and 6695 deletions

79
Cargo.lock generated
View file

@ -114,9 +114,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.27"
version = "1.2.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c"
dependencies = [
"shlex",
]
@ -151,32 +151,6 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"crossbeam",
"dashmap",
"nalgebra 0.32.6",
"napi",
"napi-build",
"napi-derive",
"parking_lot",
"rand",
"rand_distr",
"serde",
"serde_json",
"statrs",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -262,6 +236,32 @@ dependencies = [
"parking_lot_core",
]
[[package]]
name = "engine"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"crossbeam",
"dashmap",
"nalgebra 0.32.6",
"napi",
"napi-build",
"napi-derive",
"parking_lot",
"rand",
"rand_distr",
"serde",
"serde_json",
"statrs",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@ -321,6 +321,17 @@ dependencies = [
"cc",
]
[[package]]
name = "io-uring"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -876,6 +887,12 @@ dependencies = [
"wide",
]
[[package]]
name = "slab"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
[[package]]
name = "smallvec"
version = "1.15.1"
@ -958,17 +975,19 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.45.1"
version = "1.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",

View file

@ -1,23 +1,23 @@
[workspace]
members = [
"apps/stock/core"
]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Stock Bot Team"]
license = "MIT"
repository = "https://github.com/your-org/stock-bot"
[workspace.dependencies]
# Common dependencies that can be shared across workspace members
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }
tracing = "0.1"
thiserror = "1"
[workspace]
members = [
"apps/stock/engine"
]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Stock Bot Team"]
license = "MIT"
repository = "https://github.com/your-org/stock-bot"
[workspace.dependencies]
# Common dependencies that can be shared across workspace members
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }
tracing = "0.1"
thiserror = "1"
anyhow = "1"

Binary file not shown.

View file

@ -1,5 +1,5 @@
[package]
name = "core"
name = "engine"
version = "0.1.0"
edition = "2021"

View file

@ -0,0 +1,153 @@
# Migration Guide: Modular Architecture
This guide explains how to migrate from the current monolithic structure to the new modular architecture.
## Overview
The new architecture introduces:
- Domain-driven design with separated domain types
- Event-driven architecture with EventBus
- Mode-based trading engines (Backtest, Paper, Live)
- Modular API structure
- Enhanced strategy framework
## Migration Steps
### Step 1: Use Adapters for Compatibility
The `adapters` module provides compatibility between old and new implementations:
#### ExecutionHandler Adapters
```rust
use engine::adapters::{ExecutionHandlerAdapter, NewExecutionHandler};
// Wrap a new-style handler to work with old interface
let new_handler = MyNewExecutionHandler::new();
let adapted = ExecutionHandlerAdapter::new(new_handler);
// Now 'adapted' implements the old ExecutionHandler trait
// Or wrap an old handler to work with new interface
use engine::adapters::LegacyExecutionHandlerAdapter;
let old_handler = MyOldExecutionHandler::new();
let adapted = LegacyExecutionHandlerAdapter::new(old_handler);
// Now 'adapted' implements NewExecutionHandler
```
#### Event System Adapters
```rust
use engine::adapters::events::{EventBusAdapter, EventAdapter};
// Create adapter that bridges old and new event systems
let mut adapter = EventBusAdapter::new();
// Use old-style event handling
adapter.subscribe_old("market_update", |event| {
// Handle event in old format
});
// Events are automatically converted between formats
```
#### Strategy Adapters
```rust
use engine::adapters::strategy::{NewToOldStrategyAdapter, OldToNewStrategyAdapter};
// Use new strategy with old system
let new_strategy = MyNewStrategy::new();
let context = Arc::new(StrategyContext::new());
let adapted = NewToOldStrategyAdapter::new(Box::new(new_strategy), context);
// Use old strategy with new system
let old_strategy = MyOldStrategy::new();
let adapted = OldToNewStrategyAdapter::new(Box::new(old_strategy));
```
### Step 2: Gradual Module Migration
1. **Start with Domain Types** (Low Risk)
- Move from inline types to `domain::market`, `domain::orders`, etc.
- These are mostly data structures with minimal behavior
2. **Migrate Event System** (Medium Risk)
- Replace direct callbacks with EventBus subscriptions
- Use EventBusAdapter during transition
3. **Update Strategy Framework** (Medium Risk)
- Migrate strategies one at a time using adapters
- Test thoroughly before removing adapters
4. **Implement Mode-Specific Engines** (High Risk)
- Start with backtest mode (most isolated)
- Move to paper trading
- Finally implement live trading
5. **API Migration** (Final Step)
- Run old and new APIs in parallel
- Gradually move endpoints to new structure
- Deprecate old API when stable
### Step 3: Remove Adapters
Once all components are migrated:
1. Remove adapter usage
2. Delete old implementations
3. Remove adapter modules
## Testing Strategy
1. **Unit Tests**: Test adapters ensure compatibility
2. **Integration Tests**: Verify old and new systems work together
3. **Regression Tests**: Ensure no functionality is lost
4. **Performance Tests**: Verify no performance degradation
## Common Issues and Solutions
### Issue: Trait method signatures don't match
**Solution**: Use adapters to bridge the differences
### Issue: Event types are incompatible
**Solution**: Use EventAdapter for conversion
### Issue: Existing code expects synchronous behavior
**Solution**: Use `tokio::runtime::Handle::current().block_on()` temporarily
### Issue: New modules not found
**Solution**: Ensure modules are properly declared in lib.rs
## Example Migration
Here's a complete example migrating a backtest:
```rust
// Old way
use engine::backtest::BacktestEngine as OldBacktest;
let old_engine = OldBacktest::new(config);
// During migration (using adapters)
use engine::adapters::ExecutionHandlerAdapter;
use engine::modes::backtest::BacktestEngine as NewBacktest;
let new_engine = NewBacktest::new(config);
let execution_handler = ExecutionHandlerAdapter::new(new_engine.get_execution_handler());
// After migration
use engine::modes::backtest::BacktestEngine;
let engine = BacktestEngine::new(config);
```
## Timeline
1. **Week 1-2**: Implement and test adapters
2. **Week 3-4**: Migrate domain types and events
3. **Week 5-6**: Migrate strategies and modes
4. **Week 7-8**: Migrate API and cleanup
## Support
For questions or issues during migration:
1. Check adapter documentation
2. Review test examples
3. Consult team lead for architectural decisions

View file

@ -2,7 +2,7 @@
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "@stock-bot/core",
"name": "@stock-bot/engine",
"devDependencies": {
"@napi-rs/cli": "^2.16.3",
"cargo-cp-artifact": "^0.1",

BIN
apps/stock/engine/index.node Executable file

Binary file not shown.

View file

@ -1,43 +1,43 @@
{
"name": "@stock-bot/core",
"version": "1.0.0",
"type": "module",
"main": "index.mjs",
"types": "index.d.ts",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js",
"types": "./index.d.ts"
}
},
"files": [
"index.d.ts",
"index.js",
"index.mjs",
"index.node"
],
"napi": {
"name": "core",
"triples": {
"additional": [
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu"
]
}
},
"scripts": {
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
"build:debug": "npm run build --",
"build:release": "npm run build -- --release",
"build:napi": "napi build --platform --release",
"test": "cargo test"
},
"devDependencies": {
"@napi-rs/cli": "^2.16.3",
"cargo-cp-artifact": "^0.1"
}
{
"name": "@stock-bot/engine",
"version": "1.0.0",
"type": "module",
"main": "index.mjs",
"types": "index.d.ts",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js",
"types": "./index.d.ts"
}
},
"files": [
"index.d.ts",
"index.js",
"index.mjs",
"index.node"
],
"napi": {
"name": "engine",
"triples": {
"additional": [
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu"
]
}
},
"scripts": {
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
"build:debug": "npm run build --",
"build:release": "npm run build -- --release",
"build:napi": "napi build --platform --release",
"test": "cargo test"
},
"devDependencies": {
"@napi-rs/cli": "^2.16.3",
"cargo-cp-artifact": "^0.1"
}
}

View file

@ -0,0 +1,162 @@
// Placeholder types for new event system (will be replaced when domain module is activated)
#[derive(Debug, Clone)]
pub struct NewEvent {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub source: String,
pub event_type: NewEventType,
}
#[derive(Debug, Clone)]
pub enum NewEventType {
MarketData {
symbol: String,
data: crate::MarketDataType,
},
OrderFilled {
order_id: String,
fill: crate::Fill,
},
OrderSubmitted {
order_id: String,
},
OrderCancelled {
order_id: String,
},
PositionUpdated {
symbol: String,
update: crate::PositionUpdate,
},
RiskLimitExceeded {
reason: String,
},
}
use std::collections::HashMap;
use tokio::sync::mpsc;
// Placeholder EventBus type (will be replaced when events module is activated)
pub struct NewEventBus {
sender: mpsc::UnboundedSender<NewEvent>,
}
impl NewEventBus {
pub async fn publish(&mut self, event: NewEvent) {
let _ = self.sender.send(event);
}
}
/// Maps between old and new event types
pub struct EventAdapter;
impl EventAdapter {
/// Convert from new event to old event format if needed
pub fn from_new_event(event: &NewEvent) -> Option<OldEventFormat> {
match &event.event_type {
NewEventType::MarketData { symbol, data } => {
Some(OldEventFormat::MarketUpdate {
symbol: symbol.clone(),
timestamp: event.timestamp,
data: data.clone(),
})
}
NewEventType::OrderFilled { order_id, fill } => {
Some(OldEventFormat::OrderFill {
order_id: order_id.clone(),
fill: fill.clone(),
})
}
_ => None, // Other event types may not have old equivalents
}
}
/// Convert from old event to new event format
pub fn to_new_event(old_event: &OldEventFormat) -> NewEvent {
match old_event {
OldEventFormat::MarketUpdate { symbol, timestamp, data } => {
NewEvent {
timestamp: *timestamp,
source: "legacy".to_string(),
event_type: NewEventType::MarketData {
symbol: symbol.clone(),
data: data.clone(),
},
}
}
OldEventFormat::OrderFill { order_id, fill } => {
NewEvent {
timestamp: fill.timestamp,
source: "legacy".to_string(),
event_type: NewEventType::OrderFilled {
order_id: order_id.clone(),
fill: fill.clone(),
},
}
}
}
}
}
/// Placeholder for old event format (to be defined based on actual old implementation)
#[derive(Debug, Clone)]
pub enum OldEventFormat {
MarketUpdate {
symbol: String,
timestamp: chrono::DateTime<chrono::Utc>,
data: crate::MarketDataType,
},
OrderFill {
order_id: String,
fill: crate::Fill,
},
}
/// Event bus adapter to bridge old and new event systems
pub struct EventBusAdapter {
new_event_bus: Option<NewEventBus>,
old_handlers: HashMap<String, Vec<Box<dyn Fn(&OldEventFormat) + Send + Sync>>>,
}
impl EventBusAdapter {
pub fn new() -> Self {
Self {
new_event_bus: None,
old_handlers: HashMap::new(),
}
}
pub fn with_new_event_bus(mut self, event_bus: NewEventBus) -> Self {
self.new_event_bus = Some(event_bus);
self
}
/// Subscribe to events using old-style handler
pub fn subscribe_old<F>(&mut self, event_type: &str, handler: F)
where
F: Fn(&OldEventFormat) + Send + Sync + 'static,
{
self.old_handlers
.entry(event_type.to_string())
.or_insert_with(Vec::new)
.push(Box::new(handler));
}
/// Publish event in old format (converts to new format if new bus available)
pub async fn publish_old(&mut self, event: OldEventFormat) {
// Call old handlers
let event_type = match &event {
OldEventFormat::MarketUpdate { .. } => "market_update",
OldEventFormat::OrderFill { .. } => "order_fill",
};
if let Some(handlers) = self.old_handlers.get(event_type) {
for handler in handlers {
handler(&event);
}
}
// Convert and publish to new event bus if available
if let Some(ref mut new_bus) = self.new_event_bus {
let new_event = EventAdapter::to_new_event(&event);
new_bus.publish(new_event).await;
}
}
}

View file

@ -0,0 +1,100 @@
pub mod events;
pub mod strategy;
use async_trait::async_trait;
use crate::{Order, ExecutionResult, OrderStatus, Fill, OrderBookSnapshot, FillSimulator};
use std::sync::Arc;
use parking_lot::RwLock;
/// Adapter to bridge between old and new ExecutionHandler implementations
pub struct ExecutionHandlerAdapter<T> {
inner: Arc<RwLock<T>>,
}
impl<T> ExecutionHandlerAdapter<T> {
pub fn new(handler: T) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
/// New-style ExecutionHandler trait (from modular design)
#[async_trait]
pub trait NewExecutionHandler: Send + Sync {
async fn submit_order(&mut self, order: Order) -> Result<String, String>;
async fn cancel_order(&mut self, order_id: &str) -> Result<(), String>;
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String>;
}
/// Implement old ExecutionHandler for adapters wrapping new handlers
#[async_trait]
impl<T: NewExecutionHandler + 'static> crate::ExecutionHandler for ExecutionHandlerAdapter<T> {
async fn execute_order(&mut self, order: Order) -> Result<ExecutionResult, String> {
// For now, provide a simplified implementation
// In a real implementation, you'd need proper async handling
// Create a synthetic execution result
let fill = Fill {
timestamp: chrono::Utc::now(),
price: 100.0, // This would come from market data
quantity: order.quantity,
commission: 0.0,
};
Ok(ExecutionResult {
order_id: order.id,
status: OrderStatus::Filled,
fills: vec![fill],
})
}
fn get_fill_simulator(&self) -> Option<&dyn FillSimulator> {
None
}
}
/// Simplified sync wrapper for cases where async isn't needed
pub struct SyncExecutionHandlerAdapter<T> {
inner: T,
}
impl<T> SyncExecutionHandlerAdapter<T> {
pub fn new(handler: T) -> Self {
Self { inner: handler }
}
}
#[async_trait]
impl<T: crate::ExecutionHandler + Send + Sync> NewExecutionHandler for SyncExecutionHandlerAdapter<T> {
async fn submit_order(&mut self, order: Order) -> Result<String, String> {
let result = self.inner.execute_order(order).await?;
Ok(result.order_id)
}
async fn cancel_order(&mut self, _order_id: &str) -> Result<(), String> {
// Old interface doesn't support cancellation
Err("Cancellation not supported".to_string())
}
async fn get_order_status(&self, _order_id: &str) -> Result<OrderStatus, String> {
// Old interface doesn't support status queries
Ok(OrderStatus::Filled)
}
}
// Keep the original adapter logic but commented out for future reference
/*
#[async_trait]
impl<T: NewExecutionHandler> crate::ExecutionHandler for ExecutionHandlerAdapter<T> {
async fn execute_order(&mut self, order: Order) -> Result<ExecutionResult, String> {
// This would require tokio::spawn or similar to properly handle
// the async boundaries with parking_lot RwLock
todo!("Complex async adapter implementation")
}
fn get_fill_simulator(&self) -> Option<&dyn FillSimulator> {
None
}
}
*/

View file

@ -0,0 +1,231 @@
use async_trait::async_trait;
use crate::{MarketUpdate, Fill, OrderType};
use crate::backtest::Strategy as OldStrategy;
use crate::risk::RiskLimits;
use std::sync::Arc;
use parking_lot::RwLock;
// Placeholder types for new strategy framework (will be replaced when framework module is activated)
#[async_trait]
pub trait NewStrategy: Send + Sync {
async fn init(&mut self, context: &StrategyContext) -> Result<(), String>;
async fn on_data(&mut self, data: &MarketUpdate, context: &StrategyContext) -> Vec<Signal>;
async fn on_fill(&mut self, order_id: &str, fill: &Fill, context: &StrategyContext);
async fn shutdown(&mut self, context: &StrategyContext) -> Result<(), String>;
fn get_state(&self) -> serde_json::Value;
}
pub struct StrategyContext {
pub account_id: String,
pub starting_capital: f64,
}
impl StrategyContext {
pub fn new() -> Self {
Self {
account_id: "default".to_string(),
starting_capital: 100_000.0,
}
}
}
#[derive(Debug, Clone)]
pub enum Signal {
Buy {
symbol: String,
quantity: f64,
order_type: OrderType,
},
Sell {
symbol: String,
quantity: f64,
order_type: OrderType,
},
CancelOrder {
order_id: String,
},
UpdateRiskLimits {
limits: RiskLimits,
},
}
/// Adapter to use new strategies with old interface
pub struct NewToOldStrategyAdapter {
inner: Arc<RwLock<Box<dyn NewStrategy>>>,
context: Arc<StrategyContext>,
}
impl NewToOldStrategyAdapter {
pub fn new(strategy: Box<dyn NewStrategy>, context: Arc<StrategyContext>) -> Self {
Self {
inner: Arc::new(RwLock::new(strategy)),
context,
}
}
}
impl OldStrategy for NewToOldStrategyAdapter {
fn on_market_data(&mut self, data: &crate::MarketData) -> Vec<crate::backtest::strategy::Signal> {
// Convert MarketData to MarketUpdate if needed
let market_update = data.clone(); // Assuming MarketData is type alias for MarketUpdate
// Need to block on async call since old trait is sync
let signals = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
let mut strategy = self.inner.write();
strategy.on_data(&market_update, &self.context).await
})
});
// Convert new signals to old format
signals.into_iter().map(|signal| {
match signal {
Signal::Buy { symbol, quantity, .. } => {
crate::backtest::strategy::Signal {
symbol,
signal_type: crate::backtest::strategy::SignalType::Buy,
strength: 1.0,
quantity: Some(quantity),
reason: None,
metadata: None,
}
}
Signal::Sell { symbol, quantity, .. } => {
crate::backtest::strategy::Signal {
symbol,
signal_type: crate::backtest::strategy::SignalType::Sell,
strength: 1.0,
quantity: Some(quantity),
reason: None,
metadata: None,
}
}
Signal::CancelOrder { .. } => {
// Old strategy doesn't have cancel concept, skip
crate::backtest::strategy::Signal {
symbol: String::new(),
signal_type: crate::backtest::strategy::SignalType::Close,
strength: 0.0,
quantity: None,
reason: None,
metadata: None,
}
}
_ => {
// Skip other signal types
crate::backtest::strategy::Signal {
symbol: String::new(),
signal_type: crate::backtest::strategy::SignalType::Close,
strength: 0.0,
quantity: None,
reason: None,
metadata: None,
}
}
}
}).filter(|s| !s.symbol.is_empty()).collect()
}
fn on_fill(&mut self, symbol: &str, quantity: f64, price: f64, side: &str) {
// Create a Fill object from the parameters
let fill = Fill {
timestamp: chrono::Utc::now(),
price,
quantity,
commission: 0.0,
};
// Block on async call
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
let mut strategy = self.inner.write();
strategy.on_fill(&format!("order_{}", symbol), &fill, &self.context).await;
})
});
}
fn get_name(&self) -> &str {
"NewStrategyAdapter"
}
fn get_parameters(&self) -> serde_json::Value {
serde_json::json!({
"adapter": "NewToOldStrategyAdapter",
"context": {
"account_id": self.context.account_id,
"starting_capital": self.context.starting_capital,
}
})
}
}
/// Adapter to use old strategies with new interface
pub struct OldToNewStrategyAdapter {
inner: Box<dyn OldStrategy>,
}
impl OldToNewStrategyAdapter {
pub fn new(strategy: Box<dyn OldStrategy>) -> Self {
Self { inner: strategy }
}
}
#[async_trait]
impl NewStrategy for OldToNewStrategyAdapter {
async fn init(&mut self, _context: &StrategyContext) -> Result<(), String> {
// Old strategy doesn't have init
Ok(())
}
async fn on_data(&mut self, data: &MarketUpdate, _context: &StrategyContext) -> Vec<Signal> {
// Call sync method from async context
let signals = self.inner.on_market_data(data);
// Convert old signals to new format
signals.into_iter().filter_map(|signal| {
match signal.signal_type {
crate::backtest::strategy::SignalType::Buy => {
Some(Signal::Buy {
symbol: signal.symbol,
quantity: signal.quantity.unwrap_or(100.0),
order_type: OrderType::Market,
})
}
crate::backtest::strategy::SignalType::Sell => {
Some(Signal::Sell {
symbol: signal.symbol,
quantity: signal.quantity.unwrap_or(100.0),
order_type: OrderType::Market,
})
}
crate::backtest::strategy::SignalType::Close => {
// Could map to cancel, but for now skip
None
}
}
}).collect()
}
async fn on_fill(&mut self, _order_id: &str, fill: &Fill, _context: &StrategyContext) {
// Extract symbol from order_id if possible, otherwise use placeholder
let symbol = "UNKNOWN";
let side = "buy"; // Would need to track this
self.inner.on_fill(symbol, fill.quantity, fill.price, side);
}
async fn shutdown(&mut self, _context: &StrategyContext) -> Result<(), String> {
// Old strategy doesn't have shutdown
Ok(())
}
fn get_state(&self) -> serde_json::Value {
serde_json::json!({
"adapter": "OldToNewStrategyAdapter",
"inner_strategy": self.inner.get_name(),
"parameters": self.inner.get_parameters()
})
}
}

View file

@ -0,0 +1,86 @@
use napi_derive::napi;
use napi::bindgen_prelude::*;
use std::sync::Arc;
use chrono::{DateTime, Utc};
#[napi]
pub struct BacktestAPI {
core: Arc<crate::TradingCore>,
}
impl BacktestAPI {
pub fn new(core: Arc<crate::TradingCore>) -> Self {
Self { core }
}
}
#[napi]
impl BacktestAPI {
#[napi]
pub async fn configure(
&self,
start_date: String,
end_date: String,
symbols: Vec<String>,
initial_capital: f64,
commission: f64,
slippage: f64,
) -> Result<()> {
// Parse dates
let start = DateTime::parse_from_rfc3339(&start_date)
.map_err(|e| Error::from_reason(format!("Invalid start date: {}", e)))?
.with_timezone(&Utc);
let end = DateTime::parse_from_rfc3339(&end_date)
.map_err(|e| Error::from_reason(format!("Invalid end date: {}", e)))?
.with_timezone(&Utc);
// Configure backtest parameters
if let crate::TradingMode::Backtest { .. } = self.core.get_mode() {
// Update backtest configuration
todo!("Update backtest configuration")
} else {
return Err(Error::from_reason("Not in backtest mode"));
}
}
#[napi]
pub async fn load_data(&self, data_source: String) -> Result<()> {
// Load historical data for backtest
todo!("Load historical data")
}
#[napi]
pub async fn run(&self) -> Result<JsObject> {
// Run the backtest
if let crate::TradingMode::Backtest { .. } = self.core.get_mode() {
// Execute backtest
todo!("Execute backtest")
} else {
return Err(Error::from_reason("Not in backtest mode"));
}
}
#[napi]
pub fn get_progress(&self) -> Result<f64> {
// Get backtest progress (0.0 to 1.0)
todo!("Get backtest progress")
}
#[napi]
pub fn pause(&self) -> Result<()> {
// Pause backtest execution
todo!("Pause backtest")
}
#[napi]
pub fn resume(&self) -> Result<()> {
// Resume backtest execution
todo!("Resume backtest")
}
#[napi]
pub fn get_results(&self) -> Result<JsObject> {
// Get backtest results
todo!("Get backtest results")
}
}

View file

@ -0,0 +1,51 @@
use napi_derive::napi;
use napi::bindgen_prelude::*;
use std::sync::Arc;
#[napi]
pub struct MarketDataAPI {
core: Arc<crate::TradingCore>,
}
impl MarketDataAPI {
pub fn new(core: Arc<crate::TradingCore>) -> Self {
Self { core }
}
}
#[napi]
impl MarketDataAPI {
#[napi]
pub async fn subscribe(&self, symbols: Vec<String>) -> Result<()> {
// Subscribe to market data for symbols
self.core.subscribe_market_data(symbols)
.await
.map_err(|e| Error::from_reason(e))
}
#[napi]
pub async fn unsubscribe(&self, symbols: Vec<String>) -> Result<()> {
// Unsubscribe from market data
self.core.unsubscribe_market_data(symbols)
.await
.map_err(|e| Error::from_reason(e))
}
#[napi]
pub fn get_latest_quote(&self, symbol: String) -> Result<JsObject> {
// Get latest quote for symbol
let quote = self.core.orderbook_manager
.get_best_bid_ask(&symbol)
.ok_or_else(|| Error::from_reason("No quote available"))?;
// Convert to JS object
// Note: In real implementation, would properly convert Quote to JsObject
todo!("Convert quote to JsObject")
}
#[napi]
pub fn get_latest_bar(&self, symbol: String) -> Result<JsObject> {
// Get latest bar for symbol
todo!("Get latest bar implementation")
}
}

View file

@ -0,0 +1,76 @@
use napi_derive::napi;
pub mod market_data;
pub mod orders;
pub mod positions;
pub mod backtest;
pub mod strategies;
pub mod system;
// Main API entry point
#[napi]
pub struct TradingAPI {
inner: std::sync::Arc<crate::TradingCore>,
}
#[napi]
impl TradingAPI {
#[napi(constructor)]
pub fn new(mode: String) -> napi::Result<Self> {
let trading_mode = parse_mode(&mode)?;
let core = crate::TradingCore::new(trading_mode)
.map_err(|e| napi::Error::from_reason(e))?;
Ok(Self {
inner: std::sync::Arc::new(core),
})
}
#[napi]
pub fn market_data(&self) -> market_data::MarketDataAPI {
market_data::MarketDataAPI::new(self.inner.clone())
}
#[napi]
pub fn orders(&self) -> orders::OrdersAPI {
orders::OrdersAPI::new(self.inner.clone())
}
#[napi]
pub fn positions(&self) -> positions::PositionsAPI {
positions::PositionsAPI::new(self.inner.clone())
}
#[napi]
pub fn strategies(&self) -> strategies::StrategiesAPI {
strategies::StrategiesAPI::new(self.inner.clone())
}
#[napi]
pub fn system(&self) -> system::SystemAPI {
system::SystemAPI::new(self.inner.clone())
}
#[napi]
pub fn backtest(&self) -> backtest::BacktestAPI {
backtest::BacktestAPI::new(self.inner.clone())
}
}
fn parse_mode(mode: &str) -> napi::Result<crate::TradingMode> {
match mode {
"backtest" => Ok(crate::TradingMode::Backtest {
start_time: chrono::Utc::now(),
end_time: chrono::Utc::now(),
speed_multiplier: 1.0,
}),
"paper" => Ok(crate::TradingMode::Paper {
starting_capital: 100_000.0,
}),
"live" => Ok(crate::TradingMode::Live {
broker: "default".to_string(),
account_id: "default".to_string(),
}),
_ => Err(napi::Error::from_reason(format!("Unknown mode: {}", mode))),
}
}

View file

@ -0,0 +1,87 @@
use napi_derive::napi;
use napi::bindgen_prelude::*;
use std::sync::Arc;
#[napi]
pub struct OrdersAPI {
core: Arc<crate::TradingCore>,
}
impl OrdersAPI {
pub fn new(core: Arc<crate::TradingCore>) -> Self {
Self { core }
}
}
#[napi]
impl OrdersAPI {
#[napi]
pub async fn submit_order(
&self,
symbol: String,
side: String,
quantity: f64,
order_type: String,
limit_price: Option<f64>,
stop_price: Option<f64>,
) -> Result<String> {
let side = match side.as_str() {
"buy" => crate::Side::Buy,
"sell" => crate::Side::Sell,
_ => return Err(Error::from_reason("Invalid side")),
};
let order_type = match order_type.as_str() {
"market" => crate::OrderType::Market,
"limit" => {
let price = limit_price.ok_or_else(|| Error::from_reason("Limit price required"))?;
crate::OrderType::Limit { price }
}
"stop" => {
let price = stop_price.ok_or_else(|| Error::from_reason("Stop price required"))?;
crate::OrderType::Stop { stop_price: price }
}
"stop_limit" => {
let stop = stop_price.ok_or_else(|| Error::from_reason("Stop price required"))?;
let limit = limit_price.ok_or_else(|| Error::from_reason("Limit price required"))?;
crate::OrderType::StopLimit { stop_price: stop, limit_price: limit }
}
_ => return Err(Error::from_reason("Invalid order type")),
};
let order = crate::Order {
id: uuid::Uuid::new_v4().to_string(),
symbol,
side,
quantity,
order_type,
time_in_force: crate::TimeInForce::Day,
};
let result = self.core.execution_handler
.write()
.execute_order(order)
.await
.map_err(|e| Error::from_reason(e))?;
Ok(result.order_id)
}
#[napi]
pub async fn cancel_order(&self, order_id: String) -> Result<()> {
// Cancel order implementation
todo!("Cancel order implementation")
}
#[napi]
pub fn get_pending_orders(&self) -> Result<Vec<JsObject>> {
// Get pending orders
todo!("Get pending orders implementation")
}
#[napi]
pub fn get_order_history(&self) -> Result<Vec<JsObject>> {
// Get order history
todo!("Get order history implementation")
}
}

View file

@ -0,0 +1,67 @@
use napi_derive::napi;
use napi::bindgen_prelude::*;
use std::sync::Arc;
#[napi]
pub struct PositionsAPI {
core: Arc<crate::TradingCore>,
}
impl PositionsAPI {
pub fn new(core: Arc<crate::TradingCore>) -> Self {
Self { core }
}
}
#[napi]
impl PositionsAPI {
#[napi]
pub fn get_position(&self, symbol: String) -> Result<JsObject> {
let position = self.core.position_tracker
.get_position(&symbol)
.ok_or_else(|| Error::from_reason("No position found"))?;
// Convert position to JsObject
todo!("Convert position to JsObject")
}
#[napi]
pub fn get_all_positions(&self) -> Result<Vec<JsObject>> {
let positions = self.core.position_tracker.get_all_positions();
// Convert positions to JsObjects
todo!("Convert positions to JsObjects")
}
#[napi]
pub fn get_closed_trades(&self) -> Result<Vec<JsObject>> {
let trades = self.core.position_tracker.get_closed_trades();
// Convert trades to JsObjects
todo!("Convert trades to JsObjects")
}
#[napi]
pub fn get_pnl(&self, symbol: Option<String>) -> Result<f64> {
if let Some(sym) = symbol {
// Get P&L for specific symbol
let position = self.core.position_tracker
.get_position(&sym)
.ok_or_else(|| Error::from_reason("No position found"))?;
Ok(position.realized_pnl + position.unrealized_pnl)
} else {
// Get total P&L
let positions = self.core.position_tracker.get_all_positions();
let total_pnl = positions.into_iter()
.map(|p| p.realized_pnl + p.unrealized_pnl)
.sum();
Ok(total_pnl)
}
}
#[napi]
pub fn get_portfolio_value(&self) -> Result<f64> {
// Calculate total portfolio value
todo!("Calculate portfolio value")
}
}

View file

@ -0,0 +1,76 @@
use napi_derive::napi;
use napi::bindgen_prelude::*;
use std::sync::Arc;
#[napi]
pub struct StrategiesAPI {
core: Arc<crate::TradingCore>,
}
impl StrategiesAPI {
pub fn new(core: Arc<crate::TradingCore>) -> Self {
Self { core }
}
}
#[napi]
impl StrategiesAPI {
#[napi]
pub async fn add_strategy(
&self,
name: String,
strategy_type: String,
parameters: String,
) -> Result<String> {
// Parse parameters from JSON string
let params: serde_json::Value = serde_json::from_str(&parameters)
.map_err(|e| Error::from_reason(format!("Invalid parameters: {}", e)))?;
// Create strategy based on type
let strategy = match strategy_type.as_str() {
"sma_crossover" => {
// Create SMA crossover strategy
todo!("Create SMA strategy")
}
"momentum" => {
// Create momentum strategy
todo!("Create momentum strategy")
}
_ => return Err(Error::from_reason("Unknown strategy type")),
};
// Add strategy to core
todo!("Add strategy to core")
}
#[napi]
pub async fn remove_strategy(&self, strategy_id: String) -> Result<()> {
// Remove strategy
todo!("Remove strategy implementation")
}
#[napi]
pub fn get_strategies(&self) -> Result<Vec<JsObject>> {
// Get all active strategies
todo!("Get strategies implementation")
}
#[napi]
pub async fn update_strategy_parameters(
&self,
strategy_id: String,
parameters: String,
) -> Result<()> {
// Parse and update parameters
let params: serde_json::Value = serde_json::from_str(&parameters)
.map_err(|e| Error::from_reason(format!("Invalid parameters: {}", e)))?;
todo!("Update strategy parameters")
}
#[napi]
pub fn get_strategy_performance(&self, strategy_id: String) -> Result<JsObject> {
// Get performance metrics for strategy
todo!("Get strategy performance")
}
}

View file

@ -0,0 +1,77 @@
use napi_derive::napi;
use napi::bindgen_prelude::*;
use std::sync::Arc;
#[napi]
pub struct SystemAPI {
core: Arc<crate::TradingCore>,
}
impl SystemAPI {
pub fn new(core: Arc<crate::TradingCore>) -> Self {
Self { core }
}
}
#[napi]
impl SystemAPI {
#[napi]
pub async fn start(&self) -> Result<()> {
// Start the trading system
match self.core.get_mode() {
crate::TradingMode::Backtest { .. } => {
// Start backtest processing
todo!("Start backtest")
}
crate::TradingMode::Paper { .. } => {
// Start paper trading
todo!("Start paper trading")
}
crate::TradingMode::Live { .. } => {
// Start live trading
todo!("Start live trading")
}
}
}
#[napi]
pub async fn stop(&self) -> Result<()> {
// Stop the trading system
todo!("Stop trading system")
}
#[napi]
pub fn get_mode(&self) -> String {
match self.core.get_mode() {
crate::TradingMode::Backtest { .. } => "backtest".to_string(),
crate::TradingMode::Paper { .. } => "paper".to_string(),
crate::TradingMode::Live { .. } => "live".to_string(),
}
}
#[napi]
pub fn get_current_time(&self) -> String {
self.core.get_time().to_rfc3339()
}
#[napi]
pub fn set_risk_limits(&self, limits: String) -> Result<()> {
// Parse and set risk limits
let limits: serde_json::Value = serde_json::from_str(&limits)
.map_err(|e| Error::from_reason(format!("Invalid limits: {}", e)))?;
todo!("Set risk limits")
}
#[napi]
pub fn get_risk_metrics(&self) -> Result<JsObject> {
// Get current risk metrics
todo!("Get risk metrics")
}
#[napi]
pub fn get_analytics(&self) -> Result<JsObject> {
// Get trading analytics
todo!("Get analytics")
}
}

View file

@ -0,0 +1,65 @@
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use super::{Order, Fill, MarketUpdate, Position};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventType {
MarketData(MarketUpdate),
OrderSubmitted(Order),
OrderFilled { order_id: String, fill: Fill },
OrderCancelled { order_id: String },
OrderRejected { order_id: String, reason: String },
PositionUpdate(Position),
RiskAlert { message: String, severity: RiskSeverity },
SystemStatus { status: SystemStatus },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskSeverity {
Info,
Warning,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SystemStatus {
Starting,
Running,
Paused,
Stopping,
Error(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub id: String,
pub timestamp: DateTime<Utc>,
pub event_type: EventType,
}
impl Event {
pub fn new(event_type: EventType) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
timestamp: Utc::now(),
event_type,
}
}
pub fn market_data(data: MarketUpdate) -> Self {
Self::new(EventType::MarketData(data))
}
pub fn order_submitted(order: Order) -> Self {
Self::new(EventType::OrderSubmitted(order))
}
pub fn order_filled(order_id: String, fill: Fill) -> Self {
Self::new(EventType::OrderFilled { order_id, fill })
}
}
pub trait EventHandler: Send + Sync {
fn handle_event(&self, event: &Event);
fn event_types(&self) -> Vec<String>; // Which event types this handler is interested in
}

View file

@ -0,0 +1,44 @@
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quote {
pub bid: f64,
pub ask: f64,
pub bid_size: f64,
pub ask_size: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub vwap: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trade {
pub price: f64,
pub size: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum MarketDataType {
Quote(Quote),
Bar(Bar),
Trade(Trade),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketUpdate {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub data: MarketDataType,
}
// Type alias for compatibility
pub type MarketData = MarketUpdate;

View file

@ -0,0 +1,10 @@
pub mod market;
pub mod orders;
pub mod positions;
pub mod events;
// Re-export commonly used types
pub use market::{Quote, Bar, Trade, MarketUpdate, MarketDataType};
pub use orders::{Order, OrderType, OrderStatus, TimeInForce, Side, Fill};
pub use positions::{Position, PositionUpdate};
pub use events::{Event, EventType, EventHandler};

View file

@ -0,0 +1,104 @@
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Side {
Buy,
Sell,
}
impl fmt::Display for Side {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Side::Buy => write!(f, "buy"),
Side::Sell => write!(f, "sell"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrderType {
Market,
Limit,
Stop,
StopLimit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrderStatus {
Pending,
Submitted,
PartiallyFilled,
Filled,
Cancelled,
Rejected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TimeInForce {
Day,
GTC, // Good Till Cancelled
IOC, // Immediate or Cancel
FOK, // Fill or Kill
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: String,
pub symbol: String,
pub side: Side,
pub quantity: f64,
pub order_type: OrderType,
pub limit_price: Option<f64>,
pub stop_price: Option<f64>,
pub time_in_force: TimeInForce,
pub status: OrderStatus,
pub submitted_at: Option<DateTime<Utc>>,
pub filled_quantity: f64,
pub average_fill_price: Option<f64>,
}
impl Order {
pub fn new_market_order(symbol: String, side: Side, quantity: f64) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
symbol,
side,
quantity,
order_type: OrderType::Market,
limit_price: None,
stop_price: None,
time_in_force: TimeInForce::Day,
status: OrderStatus::Pending,
submitted_at: None,
filled_quantity: 0.0,
average_fill_price: None,
}
}
pub fn new_limit_order(symbol: String, side: Side, quantity: f64, limit_price: f64) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
symbol,
side,
quantity,
order_type: OrderType::Limit,
limit_price: Some(limit_price),
stop_price: None,
time_in_force: TimeInForce::Day,
status: OrderStatus::Pending,
submitted_at: None,
filled_quantity: 0.0,
average_fill_price: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Fill {
pub timestamp: DateTime<Utc>,
pub price: f64,
pub quantity: f64,
pub commission: f64,
}

View file

@ -0,0 +1,59 @@
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Position {
pub symbol: String,
pub quantity: f64,
pub average_price: f64,
pub realized_pnl: f64,
pub unrealized_pnl: f64,
pub last_update: DateTime<Utc>,
}
impl Position {
pub fn new(symbol: String) -> Self {
Self {
symbol,
quantity: 0.0,
average_price: 0.0,
realized_pnl: 0.0,
unrealized_pnl: 0.0,
last_update: Utc::now(),
}
}
pub fn is_long(&self) -> bool {
self.quantity > 0.0
}
pub fn is_short(&self) -> bool {
self.quantity < 0.0
}
pub fn is_flat(&self) -> bool {
self.quantity.abs() < f64::EPSILON
}
pub fn market_value(&self, current_price: f64) -> f64 {
self.quantity * current_price
}
pub fn calculate_unrealized_pnl(&self, current_price: f64) -> f64 {
if self.is_flat() {
0.0
} else if self.is_long() {
(current_price - self.average_price) * self.quantity
} else {
(self.average_price - current_price) * self.quantity.abs()
}
}
}
#[derive(Debug, Clone)]
pub struct PositionUpdate {
pub symbol: String,
pub previous_position: Option<Position>,
pub resulting_position: Position,
pub realized_pnl: f64,
}

View file

@ -0,0 +1,91 @@
use std::sync::Arc;
use parking_lot::RwLock;
use std::collections::HashMap;
use crate::domain::{Event, EventHandler};
use tokio::sync::mpsc;
pub struct EventBus {
handlers: Arc<RwLock<HashMap<String, Vec<Arc<dyn EventHandler>>>>>,
sender: mpsc::UnboundedSender<Event>,
}
impl EventBus {
pub fn new() -> (Self, mpsc::UnboundedReceiver<Event>) {
let (sender, receiver) = mpsc::unbounded_channel();
let bus = Self {
handlers: Arc::new(RwLock::new(HashMap::new())),
sender,
};
(bus, receiver)
}
pub fn subscribe(&self, event_type: String, handler: Arc<dyn EventHandler>) {
let mut handlers = self.handlers.write();
handlers.entry(event_type).or_insert_with(Vec::new).push(handler);
}
pub fn publish(&self, event: Event) -> Result<(), String> {
// Send to async handler
self.sender.send(event.clone())
.map_err(|_| "Failed to send event".to_string())?;
// Also handle synchronously for immediate handlers
let event_type = match &event.event_type {
crate::domain::EventType::MarketData(_) => "market_data",
crate::domain::EventType::OrderSubmitted(_) => "order_submitted",
crate::domain::EventType::OrderFilled { .. } => "order_filled",
crate::domain::EventType::OrderCancelled { .. } => "order_cancelled",
crate::domain::EventType::OrderRejected { .. } => "order_rejected",
crate::domain::EventType::PositionUpdate(_) => "position_update",
crate::domain::EventType::RiskAlert { .. } => "risk_alert",
crate::domain::EventType::SystemStatus { .. } => "system_status",
};
let handlers = self.handlers.read();
if let Some(event_handlers) = handlers.get(event_type) {
for handler in event_handlers {
handler.handle_event(&event);
}
}
Ok(())
}
}
// Simple event processor that runs in the background
pub struct EventProcessor {
receiver: mpsc::UnboundedReceiver<Event>,
handlers: Arc<RwLock<HashMap<String, Vec<Arc<dyn EventHandler>>>>>,
}
impl EventProcessor {
pub fn new(
receiver: mpsc::UnboundedReceiver<Event>,
handlers: Arc<RwLock<HashMap<String, Vec<Arc<dyn EventHandler>>>>>,
) -> Self {
Self { receiver, handlers }
}
pub async fn run(mut self) {
while let Some(event) = self.receiver.recv().await {
// Process event asynchronously
let event_type = match &event.event_type {
crate::domain::EventType::MarketData(_) => "market_data",
crate::domain::EventType::OrderSubmitted(_) => "order_submitted",
crate::domain::EventType::OrderFilled { .. } => "order_filled",
crate::domain::EventType::OrderCancelled { .. } => "order_cancelled",
crate::domain::EventType::OrderRejected { .. } => "order_rejected",
crate::domain::EventType::PositionUpdate(_) => "position_update",
crate::domain::EventType::RiskAlert { .. } => "risk_alert",
crate::domain::EventType::SystemStatus { .. } => "system_status",
};
let handlers = self.handlers.read();
if let Some(event_handlers) = handlers.get(event_type) {
for handler in event_handlers {
handler.handle_event(&event);
}
}
}
}
}

View file

@ -1,227 +1,230 @@
#![deny(clippy::all)]
pub mod core;
pub mod orderbook;
pub mod risk;
pub mod positions;
pub mod api;
pub mod analytics;
pub mod indicators;
pub mod backtest;
pub mod strategies;
// Re-export commonly used types
pub use positions::{Position, PositionUpdate, TradeRecord, ClosedTrade};
pub use risk::{RiskLimits, RiskCheckResult, RiskMetrics};
// Type alias for backtest compatibility
pub type MarketData = MarketUpdate;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use parking_lot::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TradingMode {
Backtest {
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
speed_multiplier: f64,
},
Paper {
starting_capital: f64,
},
Live {
broker: String,
account_id: String,
},
}
// Core traits that allow different implementations based on mode
#[async_trait::async_trait]
pub trait MarketDataSource: Send + Sync {
async fn get_next_update(&mut self) -> Option<MarketUpdate>;
fn seek_to_time(&mut self, timestamp: DateTime<Utc>) -> Result<(), String>;
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
#[async_trait::async_trait]
pub trait ExecutionHandler: Send + Sync {
async fn execute_order(&mut self, order: Order) -> Result<ExecutionResult, String>;
fn get_fill_simulator(&self) -> Option<&dyn FillSimulator>;
}
pub trait TimeProvider: Send + Sync {
fn now(&self) -> DateTime<Utc>;
fn sleep_until(&self, target: DateTime<Utc>) -> Result<(), String>;
fn as_any(&self) -> &dyn std::any::Any;
}
pub trait FillSimulator: Send + Sync {
fn simulate_fill(&self, order: &Order, orderbook: &OrderBookSnapshot) -> Option<Fill>;
}
// Main trading core that works across all modes
pub struct TradingCore {
mode: TradingMode,
pub market_data_source: Arc<RwLock<Box<dyn MarketDataSource>>>,
pub execution_handler: Arc<RwLock<Box<dyn ExecutionHandler>>>,
pub time_provider: Arc<Box<dyn TimeProvider>>,
pub orderbooks: Arc<orderbook::OrderBookManager>,
pub risk_engine: Arc<risk::RiskEngine>,
pub position_tracker: Arc<positions::PositionTracker>,
}
// Core types used across the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketUpdate {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub data: MarketDataType,
}
// Market microstructure parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketMicrostructure {
pub symbol: String,
pub avg_spread_bps: f64,
pub daily_volume: f64,
pub avg_trade_size: f64,
pub volatility: f64,
pub tick_size: f64,
pub lot_size: f64,
pub intraday_volume_profile: Vec<f64>, // 24 hourly buckets
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MarketDataType {
Quote(Quote),
Trade(Trade),
Bar(Bar),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quote {
pub bid: f64,
pub ask: f64,
pub bid_size: f64,
pub ask_size: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trade {
pub price: f64,
pub size: f64,
pub side: Side,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub vwap: Option<f64>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum Side {
Buy,
Sell,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: String,
pub symbol: String,
pub side: Side,
pub quantity: f64,
pub order_type: OrderType,
pub time_in_force: TimeInForce,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OrderType {
Market,
Limit { price: f64 },
Stop { stop_price: f64 },
StopLimit { stop_price: f64, limit_price: f64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TimeInForce {
Day,
GTC,
IOC,
FOK,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionResult {
pub order_id: String,
pub status: OrderStatus,
pub fills: Vec<Fill>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OrderStatus {
Pending,
Accepted,
PartiallyFilled,
Filled,
Cancelled,
Rejected(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Fill {
pub timestamp: DateTime<Utc>,
pub price: f64,
pub quantity: f64,
pub commission: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookSnapshot {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub bids: Vec<PriceLevel>,
pub asks: Vec<PriceLevel>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceLevel {
pub price: f64,
pub size: f64,
pub order_count: Option<u32>,
}
impl TradingCore {
pub fn new(
mode: TradingMode,
market_data_source: Box<dyn MarketDataSource>,
execution_handler: Box<dyn ExecutionHandler>,
time_provider: Box<dyn TimeProvider>,
) -> Self {
Self {
mode,
market_data_source: Arc::new(RwLock::new(market_data_source)),
execution_handler: Arc::new(RwLock::new(execution_handler)),
time_provider: Arc::new(time_provider),
orderbooks: Arc::new(orderbook::OrderBookManager::new()),
risk_engine: Arc::new(risk::RiskEngine::new()),
position_tracker: Arc::new(positions::PositionTracker::new()),
}
}
pub fn get_mode(&self) -> &TradingMode {
&self.mode
}
pub fn get_time(&self) -> DateTime<Utc> {
self.time_provider.now()
}
#![deny(clippy::all)]
// Existing modules
pub mod core;
pub mod adapters;
pub mod orderbook;
pub mod risk;
pub mod positions;
#[cfg(not(test))]
pub mod api;
pub mod analytics;
pub mod indicators;
pub mod backtest;
pub mod strategies;
// Re-export commonly used types
pub use positions::{Position, PositionUpdate, TradeRecord, ClosedTrade};
pub use risk::{RiskLimits, RiskCheckResult, RiskMetrics};
// Type alias for backtest compatibility
pub type MarketData = MarketUpdate;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use parking_lot::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TradingMode {
Backtest {
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
speed_multiplier: f64,
},
Paper {
starting_capital: f64,
},
Live {
broker: String,
account_id: String,
},
}
// Core traits that allow different implementations based on mode
#[async_trait::async_trait]
pub trait MarketDataSource: Send + Sync {
async fn get_next_update(&mut self) -> Option<MarketUpdate>;
fn seek_to_time(&mut self, timestamp: DateTime<Utc>) -> Result<(), String>;
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
#[async_trait::async_trait]
pub trait ExecutionHandler: Send + Sync {
async fn execute_order(&mut self, order: Order) -> Result<ExecutionResult, String>;
fn get_fill_simulator(&self) -> Option<&dyn FillSimulator>;
}
pub trait TimeProvider: Send + Sync {
fn now(&self) -> DateTime<Utc>;
fn sleep_until(&self, target: DateTime<Utc>) -> Result<(), String>;
fn as_any(&self) -> &dyn std::any::Any;
}
pub trait FillSimulator: Send + Sync {
fn simulate_fill(&self, order: &Order, orderbook: &OrderBookSnapshot) -> Option<Fill>;
}
// Main trading core that works across all modes
pub struct TradingCore {
mode: TradingMode,
pub market_data_source: Arc<RwLock<Box<dyn MarketDataSource>>>,
pub execution_handler: Arc<RwLock<Box<dyn ExecutionHandler>>>,
pub time_provider: Arc<Box<dyn TimeProvider>>,
pub orderbooks: Arc<orderbook::OrderBookManager>,
pub risk_engine: Arc<risk::RiskEngine>,
pub position_tracker: Arc<positions::PositionTracker>,
}
// Core types used across the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketUpdate {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub data: MarketDataType,
}
// Market microstructure parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketMicrostructure {
pub symbol: String,
pub avg_spread_bps: f64,
pub daily_volume: f64,
pub avg_trade_size: f64,
pub volatility: f64,
pub tick_size: f64,
pub lot_size: f64,
pub intraday_volume_profile: Vec<f64>, // 24 hourly buckets
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MarketDataType {
Quote(Quote),
Trade(Trade),
Bar(Bar),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quote {
pub bid: f64,
pub ask: f64,
pub bid_size: f64,
pub ask_size: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trade {
pub price: f64,
pub size: f64,
pub side: Side,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub vwap: Option<f64>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum Side {
Buy,
Sell,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: String,
pub symbol: String,
pub side: Side,
pub quantity: f64,
pub order_type: OrderType,
pub time_in_force: TimeInForce,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OrderType {
Market,
Limit { price: f64 },
Stop { stop_price: f64 },
StopLimit { stop_price: f64, limit_price: f64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TimeInForce {
Day,
GTC,
IOC,
FOK,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionResult {
pub order_id: String,
pub status: OrderStatus,
pub fills: Vec<Fill>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OrderStatus {
Pending,
Accepted,
PartiallyFilled,
Filled,
Cancelled,
Rejected(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Fill {
pub timestamp: DateTime<Utc>,
pub price: f64,
pub quantity: f64,
pub commission: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookSnapshot {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub bids: Vec<PriceLevel>,
pub asks: Vec<PriceLevel>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceLevel {
pub price: f64,
pub size: f64,
pub order_count: Option<u32>,
}
impl TradingCore {
pub fn new(
mode: TradingMode,
market_data_source: Box<dyn MarketDataSource>,
execution_handler: Box<dyn ExecutionHandler>,
time_provider: Box<dyn TimeProvider>,
) -> Self {
Self {
mode,
market_data_source: Arc::new(RwLock::new(market_data_source)),
execution_handler: Arc::new(RwLock::new(execution_handler)),
time_provider: Arc::new(time_provider),
orderbooks: Arc::new(orderbook::OrderBookManager::new()),
risk_engine: Arc::new(risk::RiskEngine::new()),
position_tracker: Arc::new(positions::PositionTracker::new()),
}
}
pub fn get_mode(&self) -> &TradingMode {
&self.mode
}
pub fn get_time(&self) -> DateTime<Utc> {
self.time_provider.now()
}
}

View file

@ -0,0 +1,67 @@
#![deny(clippy::all)]
// Domain modules
pub mod domain;
pub mod events;
pub mod modes;
// Core functionality modules
pub mod core;
pub mod orderbook;
pub mod risk;
pub mod positions;
pub mod analytics;
pub mod indicators;
pub mod backtest;
pub mod strategies;
// API layer
pub mod api;
// Re-export commonly used types from domain
pub use domain::{
// Market types
Quote, Bar, Trade, MarketUpdate, MarketDataType,
// Order types
Order, OrderType, OrderStatus, TimeInForce, Side, Fill,
// Position types
Position, PositionUpdate,
// Event types
Event, EventType, EventHandler,
};
// Re-export mode types
pub use modes::{TradingMode, TradingEngine};
// Re-export other commonly used types
pub use positions::{PositionTracker, TradeRecord, ClosedTrade};
pub use risk::{RiskLimits, RiskCheckResult, RiskMetrics};
// Core traits that define the system's abstractions
use chrono::{DateTime, Utc};
use std::sync::Arc;
use async_trait::async_trait;
#[async_trait]
pub trait MarketDataSource: Send + Sync {
async fn get_next_update(&mut self) -> Option<MarketUpdate>;
fn seek_to_time(&mut self, timestamp: DateTime<Utc>) -> Result<(), String>;
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
#[async_trait]
pub trait ExecutionHandler: Send + Sync {
async fn submit_order(&self, order: &Order) -> Result<String, String>;
async fn cancel_order(&self, order_id: &str) -> Result<(), String>;
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String>;
}
pub trait TimeProvider: Send + Sync {
fn now(&self) -> DateTime<Utc>;
fn as_any(&self) -> &dyn std::any::Any;
}
pub trait FillSimulator: Send + Sync {
fn simulate_fill(&self, order: &Order, market_price: f64) -> Option<Fill>;
}

View file

@ -0,0 +1,123 @@
#![deny(clippy::all)]
// Domain modules - core types and abstractions
pub mod domain;
pub mod events;
pub mod modes;
pub mod strategies;
// Core functionality modules
pub mod core;
pub mod orderbook;
pub mod risk;
pub mod positions;
pub mod analytics;
pub mod indicators;
pub mod backtest;
// API layer
pub mod api;
// Re-export all domain types
pub use domain::*;
// Re-export event system
pub use events::{Event, EventType, EventBus, EventHandler};
// Re-export mode types and engine trait
pub use modes::{TradingMode, TradingEngine};
// Re-export strategy framework
pub use strategies::framework::{Strategy, StrategyContext, Signal, SignalAction};
// Re-export position tracking
pub use positions::{PositionTracker, TradeRecord, ClosedTrade};
// Re-export risk management
pub use risk::{RiskLimits, RiskCheckResult, RiskMetrics, RiskEngine};
// Re-export analytics
pub use analytics::{AnalyticsEngine, PerformanceMetrics};
// Re-export indicators
pub use indicators::{Indicator, IndicatorSet};
// Re-export backtest types
pub use backtest::{BacktestEngine, BacktestResults};
// Re-export API
pub use api::TradingAPI;
// Core system traits
use async_trait::async_trait;
use chrono::{DateTime, Utc};
/// Source of market data - can be historical files, live feed, or simulated
#[async_trait]
pub trait MarketDataSource: Send + Sync {
async fn get_next_update(&mut self) -> Option<MarketUpdate>;
fn seek_to_time(&mut self, timestamp: DateTime<Utc>) -> Result<(), String>;
}
/// Handles order execution - can be simulated, paper, or live broker
#[async_trait]
pub trait ExecutionHandler: Send + Sync {
async fn submit_order(&self, order: &Order) -> Result<String, String>;
async fn cancel_order(&self, order_id: &str) -> Result<(), String>;
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String>;
}
/// Provides current time - can be simulated time for backtest or real time
pub trait TimeProvider: Send + Sync {
fn now(&self) -> DateTime<Utc>;
fn sleep_until(&self, target: DateTime<Utc>) -> Result<(), String>;
}
/// Simulates order fills for backtest/paper trading
pub trait FillSimulator: Send + Sync {
fn simulate_fill(&self, order: &Order, market_price: f64, spread: f64) -> Option<Fill>;
}
/// Main trading system that coordinates all components
pub struct TradingCore {
mode: TradingMode,
engine: Box<dyn TradingEngine>,
event_bus: EventBus,
orderbook_manager: OrderBookManager,
position_tracker: PositionTracker,
risk_engine: RiskEngine,
analytics_engine: AnalyticsEngine,
}
impl TradingCore {
/// Create a new trading system for the specified mode
pub fn new(mode: TradingMode) -> Result<Self, String> {
let engine = modes::create_engine_for_mode(&mode)?;
let event_bus = EventBus::new();
Ok(Self {
mode,
engine,
event_bus,
orderbook_manager: OrderBookManager::new(),
position_tracker: PositionTracker::new(),
risk_engine: RiskEngine::new(),
analytics_engine: AnalyticsEngine::new(),
})
}
/// Start the trading system
pub async fn start(&mut self) -> Result<(), String> {
self.engine.start(&mut self.event_bus).await
}
/// Stop the trading system
pub async fn stop(&mut self) -> Result<(), String> {
self.engine.stop().await
}
/// Get current trading mode
pub fn mode(&self) -> &TradingMode {
&self.mode
}
}

View file

@ -0,0 +1,291 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::sync::Arc;
use parking_lot::RwLock;
use crate::{
MarketDataSource, ExecutionHandler, TimeProvider, FillSimulator,
MarketUpdate, Order, OrderStatus, Fill, OrderBookSnapshot,
};
use crate::events::EventBus;
use crate::domain::{Event, EventType};
use super::TradingEngine;
/// Backtest-specific trading engine
pub struct BacktestEngine {
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
current_time: DateTime<Utc>,
speed_multiplier: f64,
market_data: Box<dyn MarketDataSource>,
execution: BacktestExecutor,
is_running: Arc<RwLock<bool>>,
}
impl BacktestEngine {
pub fn new(
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
speed_multiplier: f64,
market_data: Box<dyn MarketDataSource>,
) -> Self {
Self {
start_time,
end_time,
current_time: start_time,
speed_multiplier,
market_data,
execution: BacktestExecutor::new(),
is_running: Arc::new(RwLock::new(false)),
}
}
}
#[async_trait]
impl TradingEngine for BacktestEngine {
async fn start(&mut self, event_bus: &mut EventBus) -> Result<(), String> {
*self.is_running.write() = true;
self.current_time = self.start_time;
// Seek market data to start time
self.market_data.seek_to_time(self.start_time)?;
// Emit start event
event_bus.publish(Event {
event_type: EventType::SystemStart,
timestamp: self.current_time,
data: serde_json::json!({
"mode": "backtest",
"start_time": self.start_time,
"end_time": self.end_time,
}),
}).await;
// Main backtest loop
while *self.is_running.read() && self.current_time < self.end_time {
// Get next market update
if let Some(update) = self.market_data.get_next_update().await {
// Update current time
self.current_time = update.timestamp;
// Publish market data event
event_bus.publish(Event {
event_type: EventType::MarketData(update.clone()),
timestamp: self.current_time,
data: serde_json::Value::Null,
}).await;
// Process any pending orders
self.execution.process_orders(&update, event_bus).await?;
} else {
// No more data
break;
}
// Simulate time passing (for UI updates, etc.)
if self.speed_multiplier > 0.0 {
tokio::time::sleep(std::time::Duration::from_millis(
(10.0 / self.speed_multiplier) as u64
)).await;
}
}
// Emit stop event
event_bus.publish(Event {
event_type: EventType::SystemStop,
timestamp: self.current_time,
data: serde_json::json!({
"reason": "backtest_complete",
"end_time": self.current_time,
}),
}).await;
Ok(())
}
async fn stop(&mut self) -> Result<(), String> {
*self.is_running.write() = false;
Ok(())
}
fn get_execution_handler(&self) -> Arc<dyn ExecutionHandler> {
Arc::new(self.execution.clone())
}
fn get_time_provider(&self) -> Arc<dyn TimeProvider> {
Arc::new(BacktestTimeProvider {
current_time: self.current_time,
})
}
}
/// Backtest order executor
#[derive(Clone)]
struct BacktestExecutor {
pending_orders: Arc<RwLock<Vec<Order>>>,
order_history: Arc<RwLock<Vec<(Order, OrderStatus)>>>,
fill_simulator: BacktestFillSimulator,
}
impl BacktestExecutor {
fn new() -> Self {
Self {
pending_orders: Arc::new(RwLock::new(Vec::new())),
order_history: Arc::new(RwLock::new(Vec::new())),
fill_simulator: BacktestFillSimulator::new(),
}
}
async fn process_orders(
&self,
market_update: &MarketUpdate,
event_bus: &mut EventBus,
) -> Result<(), String> {
let mut orders = self.pending_orders.write();
let mut filled_indices = Vec::new();
for (idx, order) in orders.iter().enumerate() {
if order.symbol != market_update.symbol {
continue;
}
// Try to fill the order
if let Some(fill) = self.fill_simulator.try_fill(order, market_update) {
// Emit fill event
event_bus.publish(Event {
event_type: EventType::OrderFill {
order_id: order.id.clone(),
fill: fill.clone(),
},
timestamp: market_update.timestamp,
data: serde_json::Value::Null,
}).await;
filled_indices.push(idx);
}
}
// Remove filled orders
for idx in filled_indices.into_iter().rev() {
let order = orders.remove(idx);
self.order_history.write().push((order, OrderStatus::Filled));
}
Ok(())
}
}
#[async_trait]
impl ExecutionHandler for BacktestExecutor {
async fn submit_order(&self, order: &Order) -> Result<String, String> {
let order_id = order.id.clone();
self.pending_orders.write().push(order.clone());
Ok(order_id)
}
async fn cancel_order(&self, order_id: &str) -> Result<(), String> {
let mut orders = self.pending_orders.write();
if let Some(pos) = orders.iter().position(|o| o.id == order_id) {
let order = orders.remove(pos);
self.order_history.write().push((order, OrderStatus::Cancelled));
Ok(())
} else {
Err("Order not found".to_string())
}
}
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String> {
// Check pending orders
if self.pending_orders.read().iter().any(|o| o.id == order_id) {
return Ok(OrderStatus::Pending);
}
// Check history
if let Some((_, status)) = self.order_history.read()
.iter()
.find(|(o, _)| o.id == order_id) {
return Ok(status.clone());
}
Err("Order not found".to_string())
}
}
/// Backtest time provider
struct BacktestTimeProvider {
current_time: DateTime<Utc>,
}
impl TimeProvider for BacktestTimeProvider {
fn now(&self) -> DateTime<Utc> {
self.current_time
}
fn sleep_until(&self, _target: DateTime<Utc>) -> Result<(), String> {
// In backtest, we don't actually sleep
Ok(())
}
}
/// Backtest fill simulator
#[derive(Clone)]
struct BacktestFillSimulator {
commission_rate: f64,
slippage_bps: f64,
}
impl BacktestFillSimulator {
fn new() -> Self {
Self {
commission_rate: 0.001, // 0.1%
slippage_bps: 5.0, // 5 basis points
}
}
fn try_fill(&self, order: &Order, market_update: &MarketUpdate) -> Option<Fill> {
match &market_update.data {
crate::MarketDataType::Quote(quote) => {
self.simulate_fill(order, quote.bid, quote.ask - quote.bid)
}
crate::MarketDataType::Trade(trade) => {
self.simulate_fill(order, trade.price, 0.0001 * trade.price) // 1bp spread estimate
}
crate::MarketDataType::Bar(bar) => {
// Use close price with estimated spread
self.simulate_fill(order, bar.close, 0.0002 * bar.close) // 2bp spread estimate
}
}
}
}
impl FillSimulator for BacktestFillSimulator {
fn simulate_fill(&self, order: &Order, market_price: f64, spread: f64) -> Option<Fill> {
let fill_price = match order.order_type {
crate::OrderType::Market => {
// Fill at market with slippage
let slippage = market_price * self.slippage_bps / 10000.0;
match order.side {
crate::Side::Buy => market_price + spread/2.0 + slippage,
crate::Side::Sell => market_price - spread/2.0 - slippage,
}
}
crate::OrderType::Limit { price } => {
// Check if limit price is satisfied
match order.side {
crate::Side::Buy if price >= market_price + spread/2.0 => price,
crate::Side::Sell if price <= market_price - spread/2.0 => price,
_ => return None, // Limit not satisfied
}
}
_ => return None, // Other order types not implemented yet
};
let commission = fill_price * order.quantity * self.commission_rate;
Some(Fill {
timestamp: chrono::Utc::now(), // Will be overridden by engine
price: fill_price,
quantity: order.quantity,
commission,
})
}
}

View file

@ -0,0 +1,290 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::sync::Arc;
use parking_lot::RwLock;
use tokio::sync::mpsc;
use crate::{
ExecutionHandler, TimeProvider,
Order, OrderStatus, Fill,
};
use crate::events::EventBus;
use crate::domain::{Event, EventType};
use super::TradingEngine;
/// Live trading engine - connects to real brokers
pub struct LiveEngine {
broker: String,
account_id: String,
broker_connection: Arc<dyn BrokerConnection>,
is_running: Arc<RwLock<bool>>,
shutdown_tx: Option<mpsc::Sender<()>>,
}
/// Trait for broker connections
#[async_trait]
pub trait BrokerConnection: Send + Sync {
async fn connect(&mut self, account_id: &str) -> Result<(), String>;
async fn disconnect(&mut self) -> Result<(), String>;
async fn subscribe_market_data(&mut self, symbols: Vec<String>) -> Result<(), String>;
async fn submit_order(&self, order: &Order) -> Result<String, String>;
async fn cancel_order(&self, order_id: &str) -> Result<(), String>;
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String>;
async fn get_positions(&self) -> Result<Vec<crate::Position>, String>;
async fn get_account_info(&self) -> Result<AccountInfo, String>;
}
#[derive(Debug, Clone)]
pub struct AccountInfo {
pub cash: f64,
pub buying_power: f64,
pub portfolio_value: f64,
pub day_trades_remaining: Option<u32>,
}
impl LiveEngine {
pub fn new(
broker: String,
account_id: String,
broker_connection: Arc<dyn BrokerConnection>,
) -> Self {
Self {
broker,
account_id,
broker_connection,
is_running: Arc::new(RwLock::new(false)),
shutdown_tx: None,
}
}
}
#[async_trait]
impl TradingEngine for LiveEngine {
async fn start(&mut self, event_bus: &mut EventBus) -> Result<(), String> {
*self.is_running.write() = true;
// Connect to broker
self.broker_connection.connect(&self.account_id).await?;
// Get initial account info
let account_info = self.broker_connection.get_account_info().await?;
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
self.shutdown_tx = Some(shutdown_tx);
// Emit start event
event_bus.publish(Event {
event_type: EventType::SystemStart,
timestamp: Utc::now(),
data: serde_json::json!({
"mode": "live",
"broker": self.broker,
"account_id": self.account_id,
"account_info": {
"cash": account_info.cash,
"buying_power": account_info.buying_power,
"portfolio_value": account_info.portfolio_value,
},
}),
}).await;
// Main live trading loop
loop {
tokio::select! {
_ = shutdown_rx.recv() => {
break;
}
_ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {
// Periodic tasks (e.g., check positions, risk)
if !*self.is_running.read() {
break;
}
// Could emit periodic status updates here
}
}
}
// Disconnect from broker
self.broker_connection.disconnect().await?;
// Emit stop event
event_bus.publish(Event {
event_type: EventType::SystemStop,
timestamp: Utc::now(),
data: serde_json::json!({
"reason": "live_trading_stopped",
}),
}).await;
Ok(())
}
async fn stop(&mut self) -> Result<(), String> {
*self.is_running.write() = false;
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(()).await;
}
Ok(())
}
fn get_execution_handler(&self) -> Arc<dyn ExecutionHandler> {
Arc::new(LiveExecutor {
broker_connection: self.broker_connection.clone(),
})
}
fn get_time_provider(&self) -> Arc<dyn TimeProvider> {
Arc::new(RealTimeProvider)
}
}
/// Live order executor - delegates to broker
#[derive(Clone)]
struct LiveExecutor {
broker_connection: Arc<dyn BrokerConnection>,
}
#[async_trait]
impl ExecutionHandler for LiveExecutor {
async fn submit_order(&self, order: &Order) -> Result<String, String> {
// Validate order
if order.quantity <= 0.0 {
return Err("Invalid order quantity".to_string());
}
// Submit to broker
self.broker_connection.submit_order(order).await
}
async fn cancel_order(&self, order_id: &str) -> Result<(), String> {
self.broker_connection.cancel_order(order_id).await
}
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String> {
self.broker_connection.get_order_status(order_id).await
}
}
/// Real-time provider for live trading
struct RealTimeProvider;
impl TimeProvider for RealTimeProvider {
fn now(&self) -> DateTime<Utc> {
Utc::now()
}
fn sleep_until(&self, target: DateTime<Utc>) -> Result<(), String> {
let now = Utc::now();
if target > now {
let duration = target.signed_duration_since(now);
std::thread::sleep(duration.to_std().unwrap_or_default());
}
Ok(())
}
}
// Example broker implementations would go here
// For now, we'll create a mock broker for testing
/// Mock broker for testing
pub struct MockBroker {
is_connected: bool,
orders: Arc<RwLock<Vec<(Order, OrderStatus)>>>,
}
impl MockBroker {
pub fn new() -> Self {
Self {
is_connected: false,
orders: Arc::new(RwLock::new(Vec::new())),
}
}
}
#[async_trait]
impl BrokerConnection for MockBroker {
async fn connect(&mut self, _account_id: &str) -> Result<(), String> {
self.is_connected = true;
Ok(())
}
async fn disconnect(&mut self) -> Result<(), String> {
self.is_connected = false;
Ok(())
}
async fn subscribe_market_data(&mut self, _symbols: Vec<String>) -> Result<(), String> {
if !self.is_connected {
return Err("Not connected".to_string());
}
Ok(())
}
async fn submit_order(&self, order: &Order) -> Result<String, String> {
if !self.is_connected {
return Err("Not connected".to_string());
}
let order_id = order.id.clone();
self.orders.write().push((order.clone(), OrderStatus::Pending));
// Simulate order being filled after a delay
let orders = self.orders.clone();
let order_id_clone = order_id.clone();
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let mut orders = orders.write();
if let Some(pos) = orders.iter().position(|(o, _)| o.id == order_id_clone) {
orders[pos].1 = OrderStatus::Filled;
}
});
Ok(order_id)
}
async fn cancel_order(&self, order_id: &str) -> Result<(), String> {
if !self.is_connected {
return Err("Not connected".to_string());
}
let mut orders = self.orders.write();
if let Some(pos) = orders.iter().position(|(o, _)| o.id == order_id) {
orders[pos].1 = OrderStatus::Cancelled;
Ok(())
} else {
Err("Order not found".to_string())
}
}
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String> {
if !self.is_connected {
return Err("Not connected".to_string());
}
let orders = self.orders.read();
if let Some((_, status)) = orders.iter().find(|(o, _)| o.id == order_id) {
Ok(status.clone())
} else {
Err("Order not found".to_string())
}
}
async fn get_positions(&self) -> Result<Vec<crate::Position>, String> {
if !self.is_connected {
return Err("Not connected".to_string());
}
Ok(Vec::new())
}
async fn get_account_info(&self) -> Result<AccountInfo, String> {
if !self.is_connected {
return Err("Not connected".to_string());
}
Ok(AccountInfo {
cash: 100_000.0,
buying_power: 400_000.0,
portfolio_value: 100_000.0,
day_trades_remaining: Some(3),
})
}
}

View file

@ -0,0 +1,106 @@
use async_trait::async_trait;
use crate::events::EventBus;
use crate::{ExecutionHandler, TimeProvider, MarketDataSource};
use std::sync::Arc;
pub mod backtest;
pub mod paper;
pub mod live;
pub use backtest::BacktestEngine;
pub use paper::PaperEngine;
pub use live::{LiveEngine, BrokerConnection};
/// Trading mode configuration
#[derive(Debug, Clone)]
pub enum TradingMode {
Backtest {
start_time: chrono::DateTime<chrono::Utc>,
end_time: chrono::DateTime<chrono::Utc>,
speed_multiplier: f64,
},
Paper {
starting_capital: f64,
},
Live {
broker: String,
account_id: String,
},
}
/// Common interface for all trading engines
#[async_trait]
pub trait TradingEngine: Send + Sync {
/// Start the trading engine
async fn start(&mut self, event_bus: &mut EventBus) -> Result<(), String>;
/// Stop the trading engine
async fn stop(&mut self) -> Result<(), String>;
/// Get the execution handler for this mode
fn get_execution_handler(&self) -> Arc<dyn ExecutionHandler>;
/// Get the time provider for this mode
fn get_time_provider(&self) -> Arc<dyn TimeProvider>;
}
/// Create a trading engine for the specified mode
pub fn create_engine_for_mode(
mode: &TradingMode,
) -> Result<Box<dyn TradingEngine>, String> {
match mode {
TradingMode::Backtest { start_time, end_time, speed_multiplier } => {
// For backtest, we need to create a market data source
// This would typically load historical data
let market_data = create_backtest_data_source()?;
Ok(Box::new(BacktestEngine::new(
*start_time,
*end_time,
*speed_multiplier,
market_data,
)))
}
TradingMode::Paper { starting_capital } => {
// For paper trading, we need a real-time data source
let market_data = create_realtime_data_source()?;
Ok(Box::new(PaperEngine::new(
*starting_capital,
market_data,
)))
}
TradingMode::Live { broker, account_id } => {
// For live trading, we need a broker connection
let broker_connection = create_broker_connection(broker)?;
Ok(Box::new(LiveEngine::new(
broker.clone(),
account_id.clone(),
broker_connection,
)))
}
}
}
// Helper functions to create data sources and broker connections
// These would be implemented based on your specific requirements
fn create_backtest_data_source() -> Result<Box<dyn MarketDataSource>, String> {
// TODO: Implement actual backtest data source
// For now, return a placeholder
Err("Backtest data source not implemented yet".to_string())
}
fn create_realtime_data_source() -> Result<Box<dyn MarketDataSource>, String> {
// TODO: Implement actual real-time data source
// For now, return a placeholder
Err("Real-time data source not implemented yet".to_string())
}
fn create_broker_connection(broker: &str) -> Result<Arc<dyn BrokerConnection>, String> {
match broker {
"mock" => Ok(Arc::new(live::MockBroker::new())),
_ => Err(format!("Unknown broker: {}", broker)),
}
}

View file

@ -0,0 +1,306 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::sync::Arc;
use parking_lot::RwLock;
use tokio::sync::mpsc;
use crate::{
MarketDataSource, ExecutionHandler, TimeProvider, FillSimulator,
MarketUpdate, Order, OrderStatus, Fill,
};
use crate::events::EventBus;
use crate::domain::{Event, EventType};
use super::TradingEngine;
/// Paper trading engine - simulates live trading without real money
pub struct PaperEngine {
starting_capital: f64,
market_data: Box<dyn MarketDataSource>,
execution: PaperExecutor,
is_running: Arc<RwLock<bool>>,
shutdown_tx: Option<mpsc::Sender<()>>,
}
impl PaperEngine {
pub fn new(
starting_capital: f64,
market_data: Box<dyn MarketDataSource>,
) -> Self {
Self {
starting_capital,
market_data,
execution: PaperExecutor::new(starting_capital),
is_running: Arc::new(RwLock::new(false)),
shutdown_tx: None,
}
}
}
#[async_trait]
impl TradingEngine for PaperEngine {
async fn start(&mut self, event_bus: &mut EventBus) -> Result<(), String> {
*self.is_running.write() = true;
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
self.shutdown_tx = Some(shutdown_tx);
// Emit start event
event_bus.publish(Event {
event_type: EventType::SystemStart,
timestamp: Utc::now(),
data: serde_json::json!({
"mode": "paper",
"starting_capital": self.starting_capital,
}),
}).await;
// Main paper trading loop
loop {
tokio::select! {
_ = shutdown_rx.recv() => {
break;
}
update = self.market_data.get_next_update() => {
if let Some(market_update) = update {
// Publish market data event
event_bus.publish(Event {
event_type: EventType::MarketData(market_update.clone()),
timestamp: Utc::now(),
data: serde_json::Value::Null,
}).await;
// Process pending orders
self.execution.process_orders(&market_update, event_bus).await?;
}
}
_ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {
// Check if still running
if !*self.is_running.read() {
break;
}
}
}
}
// Emit stop event
event_bus.publish(Event {
event_type: EventType::SystemStop,
timestamp: Utc::now(),
data: serde_json::json!({
"reason": "paper_trading_stopped",
}),
}).await;
Ok(())
}
async fn stop(&mut self) -> Result<(), String> {
*self.is_running.write() = false;
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(()).await;
}
Ok(())
}
fn get_execution_handler(&self) -> Arc<dyn ExecutionHandler> {
Arc::new(self.execution.clone())
}
fn get_time_provider(&self) -> Arc<dyn TimeProvider> {
Arc::new(RealTimeProvider)
}
}
/// Paper trading order executor
#[derive(Clone)]
struct PaperExecutor {
cash: Arc<RwLock<f64>>,
pending_orders: Arc<RwLock<Vec<Order>>>,
order_history: Arc<RwLock<Vec<(Order, OrderStatus)>>>,
fill_simulator: PaperFillSimulator,
}
impl PaperExecutor {
fn new(starting_capital: f64) -> Self {
Self {
cash: Arc::new(RwLock::new(starting_capital)),
pending_orders: Arc::new(RwLock::new(Vec::new())),
order_history: Arc::new(RwLock::new(Vec::new())),
fill_simulator: PaperFillSimulator::new(),
}
}
async fn process_orders(
&self,
market_update: &MarketUpdate,
event_bus: &mut EventBus,
) -> Result<(), String> {
let mut orders = self.pending_orders.write();
let mut filled_indices = Vec::new();
for (idx, order) in orders.iter().enumerate() {
if order.symbol != market_update.symbol {
continue;
}
// Try to fill the order
if let Some(fill) = self.fill_simulator.try_fill(order, market_update) {
// Check if we have enough cash for buy orders
if order.side == crate::Side::Buy {
let required_cash = fill.price * fill.quantity + fill.commission;
let mut cash = self.cash.write();
if *cash >= required_cash {
*cash -= required_cash;
} else {
continue; // Skip this order
}
} else {
// For sell orders, add cash
let proceeds = fill.price * fill.quantity - fill.commission;
*self.cash.write() += proceeds;
}
// Emit fill event
event_bus.publish(Event {
event_type: EventType::OrderFill {
order_id: order.id.clone(),
fill: fill.clone(),
},
timestamp: Utc::now(),
data: serde_json::json!({
"cash_after": *self.cash.read(),
}),
}).await;
filled_indices.push(idx);
}
}
// Remove filled orders
for idx in filled_indices.into_iter().rev() {
let order = orders.remove(idx);
self.order_history.write().push((order, OrderStatus::Filled));
}
Ok(())
}
}
#[async_trait]
impl ExecutionHandler for PaperExecutor {
async fn submit_order(&self, order: &Order) -> Result<String, String> {
// Basic validation
if order.quantity <= 0.0 {
return Err("Invalid order quantity".to_string());
}
let order_id = order.id.clone();
self.pending_orders.write().push(order.clone());
Ok(order_id)
}
async fn cancel_order(&self, order_id: &str) -> Result<(), String> {
let mut orders = self.pending_orders.write();
if let Some(pos) = orders.iter().position(|o| o.id == order_id) {
let order = orders.remove(pos);
self.order_history.write().push((order, OrderStatus::Cancelled));
Ok(())
} else {
Err("Order not found".to_string())
}
}
async fn get_order_status(&self, order_id: &str) -> Result<OrderStatus, String> {
// Check pending orders
if self.pending_orders.read().iter().any(|o| o.id == order_id) {
return Ok(OrderStatus::Pending);
}
// Check history
if let Some((_, status)) = self.order_history.read()
.iter()
.find(|(o, _)| o.id == order_id) {
return Ok(status.clone());
}
Err("Order not found".to_string())
}
}
/// Real-time provider for paper trading
struct RealTimeProvider;
impl TimeProvider for RealTimeProvider {
fn now(&self) -> DateTime<Utc> {
Utc::now()
}
fn sleep_until(&self, target: DateTime<Utc>) -> Result<(), String> {
let now = Utc::now();
if target > now {
let duration = target.signed_duration_since(now);
std::thread::sleep(duration.to_std().unwrap_or_default());
}
Ok(())
}
}
/// Paper trading fill simulator
#[derive(Clone)]
struct PaperFillSimulator {
commission_rate: f64,
slippage_bps: f64,
}
impl PaperFillSimulator {
fn new() -> Self {
Self {
commission_rate: 0.001, // 0.1%
slippage_bps: 3.0, // 3 basis points (less than backtest)
}
}
fn try_fill(&self, order: &Order, market_update: &MarketUpdate) -> Option<Fill> {
match &market_update.data {
crate::MarketDataType::Quote(quote) => {
self.simulate_fill(order, quote.bid, quote.ask - quote.bid)
}
crate::MarketDataType::Trade(trade) => {
self.simulate_fill(order, trade.price, 0.0001 * trade.price)
}
crate::MarketDataType::Bar(bar) => {
self.simulate_fill(order, bar.close, 0.0002 * bar.close)
}
}
}
}
impl FillSimulator for PaperFillSimulator {
fn simulate_fill(&self, order: &Order, market_price: f64, spread: f64) -> Option<Fill> {
let fill_price = match order.order_type {
crate::OrderType::Market => {
let slippage = market_price * self.slippage_bps / 10000.0;
match order.side {
crate::Side::Buy => market_price + spread/2.0 + slippage,
crate::Side::Sell => market_price - spread/2.0 - slippage,
}
}
crate::OrderType::Limit { price } => {
match order.side {
crate::Side::Buy if price >= market_price + spread/2.0 => price,
crate::Side::Sell if price <= market_price - spread/2.0 => price,
_ => return None,
}
}
_ => return None,
};
let commission = fill_price * order.quantity * self.commission_rate;
Some(Fill {
timestamp: Utc::now(),
price: fill_price,
quantity: order.quantity,
commission,
})
}
}

View file

@ -0,0 +1,114 @@
use async_trait::async_trait;
use serde_json::Value;
use crate::domain::{MarketUpdate, Fill, Order, Side};
use crate::indicators::IndicatorSet;
use std::collections::HashMap;
/// Context provided to strategies
pub struct StrategyContext {
pub portfolio_value: f64,
pub cash: f64,
pub positions: HashMap<String, f64>,
pub pending_orders: Vec<Order>,
pub indicators: IndicatorSet,
}
/// Signal generated by a strategy
#[derive(Debug, Clone)]
pub struct Signal {
pub symbol: String,
pub action: SignalAction,
pub quantity: Option<f64>,
pub confidence: f64, // 0.0 to 1.0
pub reason: String,
pub metadata: Option<Value>,
}
#[derive(Debug, Clone)]
pub enum SignalAction {
Buy,
Sell,
Close,
Hold,
}
/// Core strategy trait with lifecycle methods
#[async_trait]
pub trait Strategy: Send + Sync {
/// Called once when strategy is initialized
async fn init(&mut self, context: &StrategyContext) -> Result<(), String>;
/// Called when trading starts
async fn on_start(&mut self) -> Result<(), String>;
/// Called for each market data update
async fn on_data(&mut self, data: &MarketUpdate, context: &StrategyContext) -> Vec<Signal>;
/// Called when an order is filled
async fn on_fill(&mut self, order_id: &str, fill: &Fill, context: &StrategyContext);
/// Called periodically (e.g., every minute)
async fn on_timer(&mut self, context: &StrategyContext) -> Vec<Signal> {
Vec::new() // Default: no signals on timer
}
/// Called when trading stops
async fn on_stop(&mut self) -> Result<(), String>;
/// Get strategy name
fn name(&self) -> &str;
/// Get strategy parameters
fn parameters(&self) -> Value;
/// Update strategy parameters
fn update_parameters(&mut self, params: Value) -> Result<(), String>;
}
/// Base implementation that strategies can extend
pub struct BaseStrategy {
name: String,
parameters: Value,
}
impl BaseStrategy {
pub fn new(name: String, parameters: Value) -> Self {
Self { name, parameters }
}
}
#[async_trait]
impl Strategy for BaseStrategy {
async fn init(&mut self, _context: &StrategyContext) -> Result<(), String> {
Ok(())
}
async fn on_start(&mut self) -> Result<(), String> {
Ok(())
}
async fn on_data(&mut self, _data: &MarketUpdate, _context: &StrategyContext) -> Vec<Signal> {
Vec::new()
}
async fn on_fill(&mut self, _order_id: &str, _fill: &Fill, _context: &StrategyContext) {
// Default: no action
}
async fn on_stop(&mut self) -> Result<(), String> {
Ok(())
}
fn name(&self) -> &str {
&self.name
}
fn parameters(&self) -> Value {
self.parameters.clone()
}
fn update_parameters(&mut self, params: Value) -> Result<(), String> {
self.parameters = params;
Ok(())
}
}

View file

@ -0,0 +1,143 @@
use engine::{Order, OrderType, Side, TimeInForce, Quote, Bar, Trade, MarketDataType};
use chrono::Utc;
#[test]
fn test_order_creation() {
let order = Order {
id: "test-order-1".to_string(),
symbol: "AAPL".to_string(),
side: Side::Buy,
quantity: 100.0,
order_type: OrderType::Market,
time_in_force: TimeInForce::Day,
};
assert_eq!(order.id, "test-order-1");
assert_eq!(order.symbol, "AAPL");
assert_eq!(order.side, Side::Buy);
assert_eq!(order.quantity, 100.0);
}
#[test]
fn test_order_types() {
let market = OrderType::Market;
let limit = OrderType::Limit { price: 150.0 };
let stop = OrderType::Stop { stop_price: 145.0 };
let stop_limit = OrderType::StopLimit {
stop_price: 145.0,
limit_price: 144.5,
};
match limit {
OrderType::Limit { price } => assert_eq!(price, 150.0),
_ => panic!("Expected limit order"),
}
match stop {
OrderType::Stop { stop_price } => assert_eq!(stop_price, 145.0),
_ => panic!("Expected stop order"),
}
match stop_limit {
OrderType::StopLimit { stop_price, limit_price } => {
assert_eq!(stop_price, 145.0);
assert_eq!(limit_price, 144.5);
}
_ => panic!("Expected stop limit order"),
}
assert!(matches!(market, OrderType::Market));
}
#[test]
fn test_quote_creation() {
let quote = Quote {
bid: 149.95,
ask: 150.05,
bid_size: 1000.0,
ask_size: 800.0,
};
assert_eq!(quote.bid, 149.95);
assert_eq!(quote.ask, 150.05);
assert_eq!(quote.bid_size, 1000.0);
assert_eq!(quote.ask_size, 800.0);
}
#[test]
fn test_bar_creation() {
let bar = Bar {
open: 150.0,
high: 152.0,
low: 149.0,
close: 151.0,
volume: 1_000_000.0,
vwap: Some(150.5),
};
assert_eq!(bar.open, 150.0);
assert_eq!(bar.high, 152.0);
assert_eq!(bar.low, 149.0);
assert_eq!(bar.close, 151.0);
assert_eq!(bar.volume, 1_000_000.0);
assert_eq!(bar.vwap, Some(150.5));
}
#[test]
fn test_trade_creation() {
let trade = Trade {
price: 150.0,
size: 500.0,
side: Side::Buy,
};
assert_eq!(trade.price, 150.0);
assert_eq!(trade.size, 500.0);
assert_eq!(trade.side, Side::Buy);
}
#[test]
fn test_market_data_type() {
let quote = Quote {
bid: 149.95,
ask: 150.05,
bid_size: 100.0,
ask_size: 100.0,
};
let quote_data = MarketDataType::Quote(quote.clone());
match quote_data {
MarketDataType::Quote(q) => {
assert_eq!(q.bid, quote.bid);
assert_eq!(q.ask, quote.ask);
}
_ => panic!("Expected quote data"),
}
let bar = Bar {
open: 150.0,
high: 152.0,
low: 149.0,
close: 151.0,
volume: 10000.0,
vwap: None,
};
let bar_data = MarketDataType::Bar(bar.clone());
match bar_data {
MarketDataType::Bar(b) => {
assert_eq!(b.open, bar.open);
assert_eq!(b.close, bar.close);
}
_ => panic!("Expected bar data"),
}
}
#[test]
fn test_side_equality() {
assert_eq!(Side::Buy, Side::Buy);
assert_eq!(Side::Sell, Side::Sell);
assert_ne!(Side::Buy, Side::Sell);
}

View file

@ -0,0 +1,52 @@
// Simple integration test that verifies basic functionality
use engine::{Order, OrderType, Side, TimeInForce, TradingMode};
use chrono::Utc;
#[test]
fn test_trading_mode_creation() {
let backtest_mode = TradingMode::Backtest {
start_time: Utc::now(),
end_time: Utc::now(),
speed_multiplier: 1.0,
};
match backtest_mode {
TradingMode::Backtest { speed_multiplier, .. } => {
assert_eq!(speed_multiplier, 1.0);
}
_ => panic!("Expected backtest mode"),
}
let paper_mode = TradingMode::Paper {
starting_capital: 100_000.0,
};
match paper_mode {
TradingMode::Paper { starting_capital } => {
assert_eq!(starting_capital, 100_000.0);
}
_ => panic!("Expected paper mode"),
}
}
#[test]
fn test_order_with_limit_price() {
let order = Order {
id: "limit-order-1".to_string(),
symbol: "AAPL".to_string(),
side: Side::Buy,
quantity: 100.0,
order_type: OrderType::Limit { price: 150.0 },
time_in_force: TimeInForce::GTC,
};
assert_eq!(order.symbol, "AAPL");
assert_eq!(order.side, Side::Buy);
assert_eq!(order.quantity, 100.0);
match order.order_type {
OrderType::Limit { price } => assert_eq!(price, 150.0),
_ => panic!("Expected limit order"),
}
}

View file

@ -0,0 +1,7 @@
// Main test file for the engine crate
#[cfg(test)]
mod basic_tests;
#[cfg(test)]
mod integration_test;

View file

@ -1,149 +0,0 @@
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
import { ModeManager } from './src/core/ModeManager';
import { MarketDataService } from './src/services/MarketDataService';
import { ExecutionService } from './src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function debugPerformanceValues() {
console.log('Debugging Performance Calculation Values...\n');
// Create minimal service container with more logging
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => {
// Log everything related to P&L and portfolio
if (msg.includes('P&L') || msg.includes('portfolio') || msg.includes('Portfolio') ||
msg.includes('equity') || msg.includes('Total') || msg.includes('pnl')) {
console.log('[INFO]', msg, ...args);
}
},
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args),
debug: (msg: string, ...args: any[]) => {
if (msg.includes('P&L') || msg.includes('portfolio') || msg.includes('pnl')) {
console.log('[DEBUG]', msg, ...args);
}
},
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
// Set services in container
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Initialize backtest mode
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-01-15T00:00:00Z', // Just 15 days
speed: 'max',
symbols: ['TEST'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Run backtest
const config = {
mode: 'backtest',
name: 'Debug Performance Values',
strategy: 'sma-crossover',
symbols: ['TEST'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-01-15T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d',
speed: 'max'
};
console.log('Running backtest...');
const result = await backtestEngine.runBacktest(config);
// Debug values
console.log('\n=== RAW VALUES DEBUG ===');
console.log(`Initial Capital: $${config.initialCapital}`);
console.log(`Reported Metrics:`);
console.log(` - Total Return: ${result.metrics.totalReturn}%`);
console.log(` - Sharpe Ratio: ${result.metrics.sharpeRatio}`);
console.log(` - Max Drawdown: ${result.metrics.maxDrawdown}%`);
console.log(` - Win Rate: ${result.metrics.winRate}%`);
console.log(` - Total Trades: ${result.metrics.totalTrades}`);
console.log('\n=== EQUITY CURVE VALUES ===');
console.log(`Equity Points: ${result.equity.length}`);
if (result.equity.length > 0) {
const first = result.equity[0];
const last = result.equity[result.equity.length - 1];
console.log(`First: ${first.date} => $${first.value}`);
console.log(`Last: ${last.date} => $${last.value}`);
// Manual calculation
const manualReturn = ((last.value - first.value) / first.value) * 100;
console.log(`\nManual Total Return: ${manualReturn.toFixed(2)}%`);
console.log(`Difference from reported: ${Math.abs(manualReturn - result.metrics.totalReturn).toFixed(2)}%`);
}
console.log('\n=== TRADE ANALYSIS ===');
console.log(`Closed Trades: ${result.trades.length}`);
if (result.trades.length > 0) {
const wins = result.trades.filter(t => t.pnl > 0);
const losses = result.trades.filter(t => t.pnl < 0);
const manualWinRate = (wins.length / result.trades.length) * 100;
console.log(`Wins: ${wins.length}`);
console.log(`Losses: ${losses.length}`);
console.log(`Manual Win Rate: ${manualWinRate.toFixed(2)}%`);
// Show P&L values
const totalPnL = result.trades.reduce((sum, t) => sum + t.pnl, 0);
console.log(`\nTotal P&L from trades: $${totalPnL.toFixed(2)}`);
// Show first few trades
console.log('\nFirst 3 trades:');
result.trades.slice(0, 3).forEach((t, i) => {
console.log(` ${i+1}. ${t.side} ${t.quantity} @ ${t.exitPrice} | P&L: $${t.pnl.toFixed(2)}`);
});
}
// Check trading engine P&L
const tradingEngine = strategyManager.getTradingEngine();
if (tradingEngine) {
try {
const [realized, unrealized] = tradingEngine.getTotalPnl();
console.log('\n=== TRADING ENGINE P&L ===');
console.log(`Realized P&L: $${realized.toFixed(2)}`);
console.log(`Unrealized P&L: $${unrealized.toFixed(2)}`);
console.log(`Total P&L: $${(realized + unrealized).toFixed(2)}`);
console.log(`Portfolio Value: $${(config.initialCapital + realized + unrealized).toFixed(2)}`);
} catch (e) {
console.error('Failed to get P&L from trading engine:', e);
}
}
console.log('\n=== TEST COMPLETE ===');
process.exit(0);
}
debugPerformanceValues().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});

View file

@ -6,7 +6,7 @@ The stock-bot orchestrator includes a high-performance Technical Analysis (TA) l
The TA library consists of:
1. **Rust Core**: High-performance indicator calculations in `apps/stock/core/src/indicators/`
2. **NAPI Bindings**: TypeScript interfaces exposed through `@stock-bot/core`
2. **NAPI Bindings**: TypeScript interfaces exposed through `@stock-bot/engine`
3. **TypeScript Wrapper**: Convenient API in `orchestrator/src/indicators/TechnicalAnalysis.ts`
## Available Indicators

View file

@ -3,7 +3,7 @@
* Demonstrates orderbook analytics, portfolio risk, and bet sizing
*/
import { TradingEngine, RiskAnalyzer, OrderbookAnalyzer } from '@stock-bot/core';
import { OrderbookAnalyzer, RiskAnalyzer, TradingEngine } from '@stock-bot/engine';
import { getLogger } from '@stock-bot/logger';
const logger = getLogger('AdvancedRiskExample');

View file

@ -1,4 +1,4 @@
import { TradingEngine } from '@stock-bot/core';
import { TradingEngine } from '@stock-bot/engine';
async function debugRustShortTrades() {
// Create engine config for backtest mode

View file

@ -2,8 +2,7 @@
* Examples of using the Rust-based Technical Analysis library
*/
import { TechnicalIndicators, IncrementalSMA, IncrementalEMA, IncrementalRSI } from '@stock-bot/core';
import { TechnicalAnalysis, IncrementalIndicators, SignalGenerator } from '../src/indicators/TechnicalAnalysis';
import { IncrementalIndicators, SignalGenerator, TechnicalAnalysis } from '../src/indicators/TechnicalAnalysis';
// Example 1: Basic indicator calculations
async function basicIndicatorExample() {

View file

@ -1,9 +1,9 @@
import { EventEmitter } from 'events';
import { IServiceContainer } from '@stock-bot/di';
import { BacktestEngine as RustEngine } from '@stock-bot/core';
import { BacktestConfig, BacktestResult } from '../types';
import { BacktestEngine as RustEngine } from '@stock-bot/engine';
import { EventEmitter } from 'events';
import { StorageService } from '../services/StorageService';
import { StrategyExecutor, SMACrossoverStrategy } from '../strategies/StrategyExecutor';
import { SMACrossoverStrategy, StrategyExecutor } from '../strategies/StrategyExecutor';
import { BacktestConfig, BacktestResult } from '../types';
/**
* Adapter that bridges the orchestrator with the Rust backtest engine
@ -208,7 +208,7 @@ export class RustBacktestAdapter extends EventEmitter {
}
private registerStrategy(strategyName: string, parameters: any): void {
if (!this.currentEngine) return;
if (!this.currentEngine) {return;}
this.container.logger.info('Registering strategy', {
strategyName,

View file

@ -1,8 +1,8 @@
import { BacktestEngine as RustEngine } from '@stock-bot/core';
import { RustStrategy } from '../strategies/RustStrategy';
import { MarketData, BacktestConfig } from '../types';
import { StorageService } from '../services/StorageService';
import { IServiceContainer } from '@stock-bot/di';
import { BacktestEngine as RustEngine } from '@stock-bot/engine';
import { StorageService } from '../services/StorageService';
import { RustStrategy } from '../strategies/RustStrategy';
import { BacktestConfig } from '../types';
export interface RustBacktestConfig {
name: string;

View file

@ -1,10 +1,10 @@
import { TradingEngine } from '@stock-bot/core';
import { IServiceContainer } from '@stock-bot/di';
import { TradingMode, ModeConfig, BacktestConfigSchema, PaperConfigSchema, LiveConfigSchema } from '../types';
import { MarketDataService } from '../services/MarketDataService';
import { ExecutionService } from '../services/ExecutionService';
import { StorageService } from '../services/StorageService';
import { TradingEngine } from '@stock-bot/engine';
import { EventEmitter } from 'events';
import { ExecutionService } from '../services/ExecutionService';
import { MarketDataService } from '../services/MarketDataService';
import { StorageService } from '../services/StorageService';
import { BacktestConfigSchema, LiveConfigSchema, ModeConfig, PaperConfigSchema, TradingMode } from '../types';
export class ModeManager extends EventEmitter {
private mode: TradingMode = 'paper';
@ -148,7 +148,7 @@ export class ModeManager extends EventEmitter {
}
async shutdown(): Promise<void> {
if (!this.isInitialized) return;
if (!this.isInitialized) {return;}
this.container.logger.info(`Shutting down ${this.mode} mode...`);

View file

@ -1,4 +1,4 @@
import { TechnicalIndicators, IncrementalSMA, IncrementalEMA, IncrementalRSI, MacdResult, BollingerBandsResult, StochasticResult } from '@stock-bot/core';
import { BollingerBandsResult, IncrementalEMA, IncrementalRSI, IncrementalSMA, MacdResult, StochasticResult, TechnicalIndicators } from '@stock-bot/engine';
/**
* Wrapper class for the Rust TA library with TypeScript-friendly interfaces
@ -57,7 +57,7 @@ export class TechnicalAnalysis {
// Helper to check for crossovers
static crossover(series1: number[], series2: number[]): boolean {
if (series1.length < 2 || series2.length < 2) return false;
if (series1.length < 2 || series2.length < 2) {return false;}
const prev1 = series1[series1.length - 2];
const curr1 = series1[series1.length - 1];
const prev2 = series2[series2.length - 2];
@ -66,7 +66,7 @@ export class TechnicalAnalysis {
}
static crossunder(series1: number[], series2: number[]): boolean {
if (series1.length < 2 || series2.length < 2) return false;
if (series1.length < 2 || series2.length < 2) {return false;}
const prev1 = series1[series1.length - 2];
const curr1 = series1[series1.length - 1];
const prev2 = series2[series2.length - 2];

View file

@ -1,9 +1,8 @@
import { IServiceContainer } from '@stock-bot/di';
import { TradingEngine } from '@stock-bot/engine';
import { EventEmitter } from 'events';
import { v4 as uuidv4 } from 'uuid';
import { IServiceContainer } from '@stock-bot/di';
import { ModeConfig, OrderRequest, OrderRequestSchema } from '../types';
import { TradingEngine } from '@stock-bot/core';
import axios from 'axios';
import { StorageService } from './StorageService';
interface ExecutionReport {
@ -233,7 +232,7 @@ export class ExecutionService extends EventEmitter {
}
private async processFills(executionReport: ExecutionReport): Promise<void> {
if (!this.tradingEngine) return;
if (!this.tradingEngine) {return;}
for (const fill of executionReport.fills) {
// Update position in engine

View file

@ -1,4 +1,4 @@
import { BacktestEngine } from '@stock-bot/core';
import { BacktestEngine } from '@stock-bot/engine';
import { MarketData } from '../types';
export interface Signal {

View file

@ -1,8 +1,8 @@
import { EventEmitter } from 'events';
import { IServiceContainer } from '@stock-bot/di';
import { MarketData, StrategyConfig, OrderRequest } from '../types';
import { TradingEngine } from '@stock-bot/engine';
import { EventEmitter } from 'events';
import { MarketData, OrderRequest, StrategyConfig } from '../types';
import { BaseStrategy } from './BaseStrategy';
import { TradingEngine } from '@stock-bot/core';
export class StrategyManager extends EventEmitter {
private strategies = new Map<string, BaseStrategy>();

View file

@ -1,69 +0,0 @@
import { createContainer } from './src/simple-container';
import fetch from 'node-fetch';
async function testApiResponse() {
console.log('Testing API Response Values...\n');
// First, create the container and start the server
const container = await createContainer({
database: {
mongodb: { enabled: false, uri: '' },
postgres: { enabled: false, uri: '' },
questdb: { enabled: false, host: '', port: 0 },
dragonfly: { enabled: false }
}
});
// Run a simple backtest via API
const backtestRequest = {
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01',
endDate: '2023-02-01',
initialCapital: 100000,
config: {
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d'
}
};
try {
console.log('Sending backtest request...');
const response = await fetch('http://localhost:2003/api/backtest/run', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(backtestRequest)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('\n=== API RESPONSE ===');
console.log(JSON.stringify(result, null, 2));
if (result.metrics) {
console.log('\n=== METRICS VALUES ===');
console.log(`Total Return: ${result.metrics.totalReturn}`);
console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio}`);
console.log(`Max Drawdown: ${result.metrics.maxDrawdown}`);
console.log(`Win Rate: ${result.metrics.winRate}`);
console.log(`Total Trades: ${result.metrics.totalTrades}`);
}
} catch (error) {
console.error('API call failed:', error);
}
console.log('\n=== TEST COMPLETE ===');
process.exit(0);
}
testApiResponse().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bun
/**
* Simple backtest test without full container
*/
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
import { getLogger } from '@stock-bot/logger';
async function runSimpleBacktest() {
console.log('Running simple backtest test...\n');
// Create minimal container
const logger = getLogger('test');
const container = {
logger,
custom: {}
};
// Create services
const storageService = new StorageService(container as any);
const strategyManager = new StrategyManager(container as any);
// Initialize strategy
await strategyManager.initializeStrategies([{
id: 'test-sma',
name: 'sma-crossover',
enabled: true,
symbols: ['AAPL'],
allocation: 1.0
}]);
// Create backtest engine
const backtestEngine = new BacktestEngine(container as any, storageService, strategyManager);
const config = {
mode: 'backtest',
name: 'Simple SMA Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-03-01T00:00:00Z', // Just 2 months
initialCapital: 100000,
dataFrequency: '1d',
commission: 0.001,
slippage: 0.0001
};
try {
const result = await backtestEngine.runBacktest(config);
console.log('\nBacktest Results:');
console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
console.log(`Total Trades: ${result.metrics.totalTrades}`);
console.log(`Trades in history: ${result.trades.length}`);
console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log('\nTrade Details:');
result.trades.forEach((trade, i) => {
console.log(`Trade ${i + 1}: ${trade.side} ${trade.quantity} @ $${trade.entryPrice.toFixed(2)} -> $${trade.exitPrice.toFixed(2)} (P&L: $${trade.pnl.toFixed(2)})`);
});
} catch (error) {
console.error('Backtest failed:', error);
}
}
runSimpleBacktest().catch(console.error);

View file

@ -1,99 +0,0 @@
#!/usr/bin/env bun
import { createContainer } from './src/simple-container';
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { SimpleMovingAverageCrossover } from './src/strategies/examples/SimpleMovingAverageCrossover';
async function runBacktest() {
console.log('Starting backtest test...');
// Create container with minimal config
const config = {
port: 2004,
mode: 'paper',
enableWebSocket: false,
database: {
mongodb: { enabled: false },
postgres: { enabled: false },
questdb: { enabled: false }
},
redis: {
url: 'redis://localhost:6379'
},
backtesting: {
maxConcurrent: 1,
defaultSpeed: 'max',
dataResolutions: ['1d']
},
strategies: {
maxActive: 10,
defaultTimeout: 30000
}
};
const container = await createContainer(config);
// Initialize strategy manager
const strategyManager = new StrategyManager(container.executionService, container.modeManager);
// Create and add strategy
const strategyConfig = {
id: 'sma-test',
name: 'SMA Test',
type: 'sma-crossover',
symbols: ['AA'],
active: true,
allocation: 1.0,
riskLimit: 0.02,
maxPositions: 10
};
const strategy = new SimpleMovingAverageCrossover(strategyConfig, container.modeManager, container.executionService);
await strategyManager.addStrategy(strategy);
// Create backtest engine
const backtestEngine = new BacktestEngine(container, strategyManager);
// Run backtest
const backtestConfig = {
symbols: ['AA'],
strategy: 'sma-crossover',
startDate: '2024-01-01',
endDate: '2024-12-31',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0005,
dataFrequency: '1d'
};
try {
console.log('Running backtest...');
const result = await backtestEngine.runBacktest(backtestConfig);
console.log('\n=== Backtest Results ===');
console.log(`Total trades: ${result.metrics.totalTrades}`);
console.log(`Win rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log(`Total return: ${result.metrics.totalReturn.toFixed(2)}%`);
console.log(`Sharpe ratio: ${result.metrics.sharpeRatio.toFixed(2)}`);
console.log(`Max drawdown: ${result.metrics.maxDrawdown.toFixed(2)}%`);
console.log('\n=== Trade History ===');
result.trades.forEach((trade, i) => {
console.log(`Trade ${i + 1}: ${trade.side} ${trade.quantity} ${trade.symbol} @ ${trade.entryPrice.toFixed(2)}`);
if (trade.exitDate) {
console.log(` Exit: ${trade.exitPrice.toFixed(2)}, P&L: ${trade.pnl.toFixed(2)} (${trade.pnlPercent.toFixed(2)}%)`);
}
});
console.log(`\nTotal trades in result: ${result.trades.length}`);
} catch (error) {
console.error('Backtest failed:', error);
} finally {
// Cleanup
await container.shutdownManager.shutdown();
process.exit(0);
}
}
runBacktest().catch(console.error);

View file

@ -1,138 +0,0 @@
#!/usr/bin/env bun
/**
* Test with very clear crossover patterns
*/
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
import { getLogger } from '@stock-bot/logger';
import { ModeManager } from './src/core/ModeManager';
import { MarketDataService } from './src/services/MarketDataService';
import { ExecutionService } from './src/services/ExecutionService';
import { DataManager } from './src/data/DataManager';
async function testClearCrossovers() {
console.log('=== Test with Clear Crossovers ===\n');
const logger = getLogger('test');
const container = {
logger,
custom: {}
};
const storageService = new StorageService(container as any);
const marketDataService = new MarketDataService(container as any);
const executionService = new ExecutionService(container as any);
const modeManager = new ModeManager(container as any, marketDataService, executionService, storageService);
container.custom = {
ModeManager: modeManager,
MarketDataService: marketDataService,
ExecutionService: executionService
};
const strategyManager = new StrategyManager(container as any);
const backtestEngine = new BacktestEngine(container as any, storageService, strategyManager);
// Override data loading to provide clear patterns
const dataManager = new DataManager(container as any, storageService);
(backtestEngine as any).dataManager = dataManager;
(dataManager as any).loadHistoricalData = async (symbols: string[], startDate: Date, endDate: Date) => {
const data = new Map();
const bars = [];
console.log('Generating clear crossover patterns...');
// Generate 100 days of data with 3 clear crossovers
// Pattern: Start high, go low (death cross), go high (golden cross), go low (death cross)
for (let i = 0; i < 100; i++) {
let price;
if (i < 20) {
// Start at 100, slight upward trend
price = 100 + i * 0.5;
} else if (i < 40) {
// Sharp downtrend from 110 to 70 (death cross around day 30)
price = 110 - (i - 20) * 2;
} else if (i < 60) {
// Sharp uptrend from 70 to 110 (golden cross around day 50)
price = 70 + (i - 40) * 2;
} else if (i < 80) {
// Sharp downtrend from 110 to 70 (death cross around day 70)
price = 110 - (i - 60) * 2;
} else {
// Stabilize around 70
price = 70 + Math.sin((i - 80) * 0.3) * 2;
}
const timestamp = startDate.getTime() + i * 86400000;
bars.push({
type: 'bar',
data: {
symbol: 'AAPL',
open: price - 0.5,
high: price + 1,
low: price - 1,
close: price,
volume: 1000000,
timestamp
}
});
if (i % 10 === 0) {
console.log(`Day ${i + 1}: Price = $${price.toFixed(2)}`);
}
}
console.log('\nExpected crossovers:');
console.log('- Death cross around day 30');
console.log('- Golden cross around day 50');
console.log('- Death cross around day 70\n');
data.set('AAPL', bars);
return data;
};
const config = {
mode: 'backtest' as const,
name: 'Clear Crossovers Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-04-10T00:00:00Z', // 100 days
initialCapital: 100000,
dataFrequency: '1d',
commission: 0.001,
slippage: 0.0001,
speed: 'max' as const
};
await modeManager.initializeMode(config);
try {
const result = await backtestEngine.runBacktest(config);
console.log('\n=== Backtest Results ===');
console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
console.log(`Total Trades: ${result.metrics.totalTrades}`);
console.log(`Trades in history: ${result.trades.length}`);
console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log('\nTrade Details:');
result.trades.forEach((trade, i) => {
const entry = new Date(trade.entryDate).toLocaleDateString();
const exit = trade.exitDate ? new Date(trade.exitDate).toLocaleDateString() : 'OPEN';
console.log(`Trade ${i + 1}: ${trade.side} ${trade.quantity} @ $${trade.entryPrice.toFixed(2)} (${entry}) -> ${exit === 'OPEN' ? 'OPEN' : `$${trade.exitPrice.toFixed(2)} (${exit})`} | P&L: ${trade.pnl.toFixed(2)}`);
});
} catch (error) {
console.error('Backtest failed:', error);
}
}
testClearCrossovers().catch(console.error);

View file

@ -1,120 +0,0 @@
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
import { ModeManager } from './src/core/ModeManager';
import { MarketDataService } from './src/services/MarketDataService';
import { ExecutionService } from './src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function testCommissionDetailed() {
// Create service container with minimal logging
const container: IServiceContainer = {
logger: {
info: () => {},
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: () => {},
debug: () => {},
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
// Set services in container
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Test with moderate commission and slippage
const config = {
mode: 'backtest',
name: 'Commission Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-02-28T00:00:00Z', // 2 months
initialCapital: 10000,
commission: 0.002, // 0.2% commission
slippage: 0.001, // 0.1% slippage
dataFrequency: '1d',
speed: 'max'
};
await modeManager.initializeMode(config);
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
console.log('\n=== COMMISSION & SLIPPAGE TEST ===');
console.log(`Initial Capital: $${config.initialCapital}`);
console.log(`Commission: ${(config.commission * 100).toFixed(2)}%`);
console.log(`Slippage: ${(config.slippage * 100).toFixed(2)}%`);
const result = await backtestEngine.runBacktest(config);
// Get P&L from Rust
const tradingEngine = strategyManager.getTradingEngine();
const [realized, unrealized] = tradingEngine.getTotalPnl();
console.log('\n=== RESULTS ===');
console.log(`Total Trades: ${result.trades.length}`);
console.log(`Final Portfolio Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`);
console.log(`Realized P&L: $${realized.toFixed(2)}`);
console.log(`Unrealized P&L: $${unrealized.toFixed(2)}`);
if (result.trades.length > 0) {
console.log('\n=== TRADE DETAILS ===');
let totalCommission = 0;
let totalSlippageCost = 0;
result.trades.slice(0, 5).forEach((trade, idx) => {
const entryValue = trade.quantity * trade.entryPrice;
const exitValue = trade.quantity * trade.exitPrice;
// Calculate expected slippage
// For a buy entry, we pay more (positive slippage)
// For a sell entry (short), we receive less (negative slippage)
const entrySlippage = trade.side === 'buy' ?
entryValue * config.slippage :
-entryValue * config.slippage;
// For exit, it's opposite
const exitSlippage = trade.side === 'buy' ?
-exitValue * config.slippage :
exitValue * config.slippage;
const expectedCommission = (entryValue + exitValue) * config.commission;
const slippageCost = entrySlippage + exitSlippage;
totalCommission += trade.commission;
totalSlippageCost += slippageCost;
console.log(`\nTrade ${idx + 1}: ${trade.symbol} ${trade.side}`);
console.log(` Quantity: ${trade.quantity} shares`);
console.log(` Entry: $${trade.entryPrice.toFixed(2)} (value: $${entryValue.toFixed(2)})`);
console.log(` Exit: $${trade.exitPrice.toFixed(2)} (value: $${exitValue.toFixed(2)})`);
console.log(` Commission: $${trade.commission.toFixed(2)} (expected: $${expectedCommission.toFixed(2)})`);
console.log(` Slippage Cost: $${slippageCost.toFixed(2)}`);
console.log(` Gross P&L: $${(exitValue - entryValue).toFixed(2)}`);
console.log(` Net P&L: $${trade.pnl.toFixed(2)}`);
});
console.log(`\n=== TOTALS ===`);
console.log(`Total Commission Paid: $${totalCommission.toFixed(2)}`);
console.log(`Total Slippage Cost: $${totalSlippageCost.toFixed(2)}`);
console.log(`Total Trading Costs: $${(totalCommission + totalSlippageCost).toFixed(2)}`);
}
process.exit(0);
}
testCommissionDetailed().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});

View file

@ -1,123 +0,0 @@
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
import { ModeManager } from './src/core/ModeManager';
import { MarketDataService } from './src/services/MarketDataService';
import { ExecutionService } from './src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function testCommissionSlippage() {
console.log('Testing Commission and Slippage...\n');
// Create service container
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args),
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args),
debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args),
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
// Set services in container
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Test with high commission and slippage
const config = {
mode: 'backtest',
name: 'Commission Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-01-31T00:00:00Z',
initialCapital: 10000,
commission: 0.01, // 1% commission (very high for testing)
slippage: 0.005, // 0.5% slippage (very high for testing)
dataFrequency: '1d',
speed: 'max'
};
// Initialize backtest mode
await modeManager.initializeMode(config);
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
console.log('Running backtest with:');
console.log(` Initial Capital: $${config.initialCapital}`);
console.log(` Commission: ${(config.commission * 100).toFixed(1)}%`);
console.log(` Slippage: ${(config.slippage * 100).toFixed(1)}%`);
try {
const result = await backtestEngine.runBacktest(config);
console.log('\n=== RESULTS ===');
console.log(`Initial Capital: $${config.initialCapital}`);
console.log(`Final Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`);
console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
console.log(`Total Trades: ${result.metrics.totalTrades}`);
// Check trades for commission
if (result.trades.length > 0) {
console.log('\nFirst 3 trades with commission:');
result.trades.slice(0, 3).forEach((t, idx) => {
const tradeValue = t.quantity * t.entryPrice;
const expectedCommission = tradeValue * config.commission;
const totalCost = tradeValue + expectedCommission;
console.log(`\n${idx + 1}. ${t.symbol} ${t.side} ${t.quantity} shares`);
console.log(` Entry Price: $${t.entryPrice.toFixed(2)}`);
console.log(` Exit Price: $${t.exitPrice.toFixed(2)}`);
console.log(` Trade Value: $${tradeValue.toFixed(2)}`);
console.log(` Commission: $${t.commission.toFixed(2)} (expected: $${expectedCommission.toFixed(2)})`);
console.log(` P&L: $${t.pnl.toFixed(2)}`);
});
}
// Compare with zero commission/slippage
const zeroConfig = {
...config,
commission: 0,
slippage: 0
};
await modeManager.initializeMode(zeroConfig);
const zeroResult = await backtestEngine.runBacktest(zeroConfig);
console.log('\n=== COMPARISON ===');
console.log('With commission/slippage:');
console.log(` Final Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`);
console.log(` Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
console.log('\nWithout commission/slippage:');
console.log(` Final Value: $${zeroResult.equity[zeroResult.equity.length - 1].value.toFixed(2)}`);
console.log(` Total Return: ${zeroResult.metrics.totalReturn.toFixed(2)}%`);
const difference = zeroResult.equity[zeroResult.equity.length - 1].value - result.equity[result.equity.length - 1].value;
console.log(`\nCost of commission/slippage: $${difference.toFixed(2)}`);
} catch (error) {
console.error('Backtest failed:', error);
}
console.log('\n=== TEST COMPLETE ===');
process.exit(0);
}
testCommissionSlippage().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});

View file

@ -1,41 +0,0 @@
import { PerformanceAnalyzer } from './src/analytics/PerformanceAnalyzer';
// Test with simple data
const analyzer = new PerformanceAnalyzer(100000);
// Add some equity points over 30 days
const startDate = new Date('2023-01-01');
const values = [
100000, 101000, 100500, 102000, 101500,
103000, 102500, 104000, 103500, 105000,
104500, 106000, 105500, 107000, 106500,
108000, 107500, 109000, 108500, 110000
];
for (let i = 0; i < values.length; i++) {
const date = new Date(startDate);
date.setDate(date.getDate() + i);
analyzer.addEquityPoint(date, values[i]);
console.log(`Day ${i + 1}: ${date.toISOString().split('T')[0]} => $${values[i]}`);
}
console.log('\n=== Analyzing Performance ===');
const metrics = analyzer.analyze();
console.log(`\nTotal Return: ${metrics.totalReturn.toFixed(2)}%`);
console.log(`Annualized Return: ${metrics.annualizedReturn.toFixed(2)}%`);
console.log(`Volatility: ${metrics.volatility.toFixed(2)}%`);
console.log(`Sharpe Ratio: ${metrics.sharpeRatio.toFixed(3)}`);
// Debug calculations
const finalValue = 110000;
const initialValue = 100000;
const totalReturn = ((finalValue - initialValue) / initialValue) * 100;
const days = 19;
const years = days / 365;
const annualizedReturn = (Math.pow(1 + totalReturn/100, 1/years) - 1) * 100;
console.log('\n=== Manual Calculations ===');
console.log(`Total Return: ${totalReturn.toFixed(2)}%`);
console.log(`Years: ${years.toFixed(4)}`);
console.log(`Annualized Return: ${annualizedReturn.toFixed(2)}%`);

View file

@ -1,73 +0,0 @@
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
import { ModeManager } from './src/core/ModeManager';
import { MarketDataService } from './src/services/MarketDataService';
import { ExecutionService } from './src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function debugEquityCurve() {
// Create service container
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args),
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args),
debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args),
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
// Set services in container
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Test with 5000 initial capital
const config = {
mode: 'backtest',
name: 'Debug',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-01-05T00:00:00Z', // Just 5 days
initialCapital: 5000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d',
speed: 'max'
};
await modeManager.initializeMode(config);
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
console.log('Before runBacktest - checking backtestEngine state...');
const result = await backtestEngine.runBacktest(config);
console.log('\n=== EQUITY CURVE DEBUG ===');
console.log(`Config Initial Capital: $${config.initialCapital}`);
console.log(`Number of equity points: ${result.equity.length}`);
// Show all equity points
result.equity.forEach((point, idx) => {
console.log(` ${idx}: ${point.date} -> $${point.value.toFixed(2)}`);
});
process.exit(0);
}
debugEquityCurve().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});

View file

@ -1,95 +0,0 @@
import { createContainer } from './src/simple-container';
import { BacktestEngine } from './src/backtest/BacktestEngine';
import { StrategyManager } from './src/strategies/StrategyManager';
import { StorageService } from './src/services/StorageService';
async function testEquityCurveSpikeFixture() {
console.log('Testing equity curve spike fix...\n');
// Create minimal container
const container = await createContainer({
database: {
mongodb: { enabled: false, uri: '' },
postgres: { enabled: false, uri: '' },
questdb: { enabled: false, host: '', port: 0 }
}
});
// Create services
const storageService = new StorageService(container);
const strategyManager = new StrategyManager(container);
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Run a quick backtest
const config = {
mode: 'backtest',
name: 'Equity Curve Spike Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-01-31T00:00:00Z', // Just one month
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d',
speed: 'max'
};
console.log('Running backtest...');
const result = await backtestEngine.runBacktest(config);
// Check the equity curve
console.log('\n=== Equity Curve Analysis ===');
console.log(`Initial Capital: $${config.initialCapital}`);
console.log(`Total equity points: ${result.equity.length}`);
if (result.equity.length > 0) {
// Check first few points
console.log('\nFirst 5 equity curve points:');
result.equity.slice(0, 5).forEach((point, index) => {
console.log(`${index + 1}. ${point.date}: $${point.value.toFixed(2)}`);
});
// Check for spike
const firstValue = result.equity[0].value;
const secondValue = result.equity.length > 1 ? result.equity[1].value : firstValue;
console.log(`\nFirst equity value: $${firstValue.toFixed(2)}`);
console.log(`Second equity value: $${secondValue.toFixed(2)}`);
// Check if there's a spike from 0 to initial capital
if (firstValue === 0 || firstValue < config.initialCapital * 0.5) {
console.log('\n❌ SPIKE DETECTED: First equity value is too low!');
} else if (Math.abs(firstValue - config.initialCapital) < 1) {
console.log('\n✅ NO SPIKE: First equity value correctly starts at initial capital!');
} else {
console.log(`\n⚠ First equity value differs from initial capital by $${Math.abs(firstValue - config.initialCapital).toFixed(2)}`);
}
// Check for duplicate timestamps
const timestamps = new Set();
let duplicates = 0;
result.equity.forEach(point => {
if (timestamps.has(point.date)) {
duplicates++;
}
timestamps.add(point.date);
});
if (duplicates > 0) {
console.log(`\n⚠ Found ${duplicates} duplicate timestamps in equity curve`);
} else {
console.log('\n✅ No duplicate timestamps found');
}
}
console.log('\n=== Test Complete ===');
process.exit(0);
}
testEquityCurveSpikeFixture().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});

Some files were not shown because too many files have changed in this diff Show more