messy work. backtests / mock-data
This commit is contained in:
parent
4e4a048988
commit
fa70ada2bb
51 changed files with 2576 additions and 887 deletions
|
|
@ -1,251 +1,21 @@
|
|||
const { existsSync, readFileSync } = require('fs')
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
const { existsSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
let nativeBinding = null
|
||||
let localFileExisted = false
|
||||
let loadError = null
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process').execSync('which ldd 2>/dev/null', { encoding: 'utf8' })
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
// Try to load the native binding
|
||||
try {
|
||||
if (existsSync(join(__dirname, 'index.node'))) {
|
||||
nativeBinding = require('./index.node')
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
throw new Error('index.node not found')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load native binding: ${e.message}`)
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(join(__dirname, 'core.android-arm64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.android-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-android-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(join(__dirname, 'core.android-arm-eabi.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.android-arm-eabi.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-android-arm-eabi')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Android ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.win32-x64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.win32-x64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-win32-x64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'ia32':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.win32-ia32-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.win32-ia32-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-win32-ia32-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.win32-arm64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.win32-arm64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-win32-arm64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
localFileExisted = existsSync(join(__dirname, 'core.darwin-universal.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.darwin-universal.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-darwin-universal')
|
||||
}
|
||||
break
|
||||
} catch {}
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(join(__dirname, 'core.darwin-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.darwin-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-darwin-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.darwin-arm64.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.darwin-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-darwin-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'freebsd':
|
||||
if (arch !== 'x64') {
|
||||
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
||||
}
|
||||
localFileExisted = existsSync(join(__dirname, 'core.freebsd-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.freebsd-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-freebsd-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.linux-x64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.linux-x64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-linux-x64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.linux-arm64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.linux-arm64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-linux-arm64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'core.linux-arm-gnueabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./core.linux-arm-gnueabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('@stock-bot/core-linux-arm-gnueabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
|
||||
if (!nativeBinding) {
|
||||
if (loadError) {
|
||||
throw loadError
|
||||
}
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { TradingEngine } = nativeBinding
|
||||
|
||||
module.exports.TradingEngine = TradingEngine
|
||||
// Export all bindings
|
||||
module.exports = nativeBinding
|
||||
29
apps/stock/core/index.mjs
Normal file
29
apps/stock/core/index.mjs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// ESM wrapper for the native module
|
||||
import { createRequire } from 'module';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const nativeBinding = require(join(__dirname, 'index.node'));
|
||||
|
||||
export const {
|
||||
TradingEngine,
|
||||
MarketData,
|
||||
MarketUpdate,
|
||||
Order,
|
||||
Fill,
|
||||
Position,
|
||||
RiskLimits,
|
||||
RiskMetrics,
|
||||
ExecutionResult,
|
||||
OrderBookLevel,
|
||||
OrderBookSnapshot,
|
||||
MarketMicrostructure,
|
||||
PositionUpdate,
|
||||
RiskCheckResult
|
||||
} = nativeBinding;
|
||||
|
||||
export default nativeBinding;
|
||||
Binary file not shown.
|
|
@ -1,11 +1,20 @@
|
|||
{
|
||||
"name": "@stock-bot/core",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"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": {
|
||||
|
|
|
|||
|
|
@ -224,9 +224,8 @@ impl TradingEngine {
|
|||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_microstructure(&self, _symbol: String, microstructure_json: String) -> Result<()> {
|
||||
let _microstructure: MarketMicrostructure = serde_json::from_str(µstructure_json)
|
||||
.map_err(|e| Error::from_reason(format!("Failed to parse microstructure: {}", e)))?;
|
||||
pub fn set_microstructure(&self, symbol: String, microstructure_js: JsObject) -> Result<()> {
|
||||
let microstructure = parse_microstructure(microstructure_js)?;
|
||||
|
||||
let _core = self.core.lock();
|
||||
// Store microstructure for use in fill simulation
|
||||
|
|
@ -236,10 +235,43 @@ impl TradingEngine {
|
|||
|
||||
#[napi]
|
||||
pub fn load_historical_data(&self, data_json: String) -> Result<()> {
|
||||
let _data: Vec<MarketUpdate> = serde_json::from_str(&data_json)
|
||||
let data: Vec<MarketUpdate> = serde_json::from_str(&data_json)
|
||||
.map_err(|e| Error::from_reason(format!("Failed to parse data: {}", e)))?;
|
||||
|
||||
// In real implementation, would load into historical data source
|
||||
let core = self.core.lock();
|
||||
|
||||
// Downcast to HistoricalDataSource if in backtest mode
|
||||
if let TradingMode::Backtest { .. } = core.get_mode() {
|
||||
let mut data_source = core.market_data_source.write();
|
||||
if let Some(historical_source) = data_source.as_any_mut().downcast_mut::<crate::core::market_data_sources::HistoricalDataSource>() {
|
||||
historical_source.load_data(data);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn generate_mock_data(&self, symbol: String, start_time: i64, end_time: i64, seed: Option<u32>) -> Result<()> {
|
||||
let core = self.core.lock();
|
||||
|
||||
// Only available in backtest mode
|
||||
if let TradingMode::Backtest { .. } = core.get_mode() {
|
||||
let mut data_source = core.market_data_source.write();
|
||||
if let Some(historical_source) = data_source.as_any_mut().downcast_mut::<crate::core::market_data_sources::HistoricalDataSource>() {
|
||||
let start_dt = DateTime::<Utc>::from_timestamp_millis(start_time)
|
||||
.ok_or_else(|| Error::from_reason("Invalid start time"))?;
|
||||
let end_dt = DateTime::<Utc>::from_timestamp_millis(end_time)
|
||||
.ok_or_else(|| Error::from_reason("Invalid end time"))?;
|
||||
|
||||
historical_source.generate_mock_data(symbol, start_dt, end_dt, seed.map(|s| s as u64));
|
||||
} else {
|
||||
return Err(Error::from_reason("Failed to access historical data source"));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::from_reason("Mock data generation only available in backtest mode"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -323,4 +355,20 @@ fn parse_risk_limits(limits_js: JsObject) -> Result<RiskLimits> {
|
|||
max_gross_exposure: limits_js.get_named_property("maxGrossExposure")?,
|
||||
max_symbol_exposure: limits_js.get_named_property("maxSymbolExposure")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_microstructure(microstructure_js: JsObject) -> Result<MarketMicrostructure> {
|
||||
let intraday_volume_profile: Vec<f64> = microstructure_js.get_named_property("intradayVolumeProfile")
|
||||
.unwrap_or_else(|_| vec![1.0/24.0; 24]);
|
||||
|
||||
Ok(MarketMicrostructure {
|
||||
symbol: microstructure_js.get_named_property("symbol")?,
|
||||
avg_spread_bps: microstructure_js.get_named_property("avgSpreadBps")?,
|
||||
daily_volume: microstructure_js.get_named_property("dailyVolume")?,
|
||||
avg_trade_size: microstructure_js.get_named_property("avgTradeSize")?,
|
||||
volatility: microstructure_js.get_named_property("volatility")?,
|
||||
tick_size: microstructure_js.get_named_property("tickSize")?,
|
||||
lot_size: microstructure_js.get_named_property("lotSize")?,
|
||||
intraday_volume_profile,
|
||||
})
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ use crate::{MarketDataSource, MarketUpdate};
|
|||
use chrono::{DateTime, Utc};
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::VecDeque;
|
||||
use super::mock_data_generator::MockDataGenerator;
|
||||
|
||||
// Historical data source for backtesting
|
||||
pub struct HistoricalDataSource {
|
||||
|
|
@ -24,6 +25,19 @@ impl HistoricalDataSource {
|
|||
queue.extend(data);
|
||||
*self.current_position.lock() = 0;
|
||||
}
|
||||
|
||||
// Generate mock data for testing
|
||||
pub fn generate_mock_data(
|
||||
&self,
|
||||
symbol: String,
|
||||
start_time: DateTime<Utc>,
|
||||
end_time: DateTime<Utc>,
|
||||
seed: Option<u64>
|
||||
) {
|
||||
let mut generator = MockDataGenerator::new(seed.unwrap_or(42));
|
||||
let data = generator.generate_mixed_data(symbol, start_time, end_time);
|
||||
self.load_data(data);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
|
|
|||
229
apps/stock/core/src/core/mock_data_generator.rs
Normal file
229
apps/stock/core/src/core/mock_data_generator.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
use crate::{MarketUpdate, MarketDataType, Quote, Trade, Bar, Side};
|
||||
use chrono::{DateTime, Utc, Duration};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::rngs::StdRng;
|
||||
use rand_distr::{Normal, Distribution};
|
||||
|
||||
pub struct MockDataGenerator {
|
||||
rng: StdRng,
|
||||
base_price: f64,
|
||||
volatility: f64,
|
||||
spread_bps: f64,
|
||||
volume_mean: f64,
|
||||
volume_std: f64,
|
||||
}
|
||||
|
||||
impl MockDataGenerator {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self {
|
||||
rng: StdRng::seed_from_u64(seed),
|
||||
base_price: 100.0,
|
||||
volatility: 0.02, // 2% daily volatility
|
||||
spread_bps: 5.0, // 5 basis points spread
|
||||
volume_mean: 1_000_000.0,
|
||||
volume_std: 200_000.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_params(seed: u64, base_price: f64, volatility: f64, spread_bps: f64) -> Self {
|
||||
Self {
|
||||
rng: StdRng::seed_from_u64(seed),
|
||||
base_price,
|
||||
volatility,
|
||||
spread_bps,
|
||||
volume_mean: 1_000_000.0,
|
||||
volume_std: 200_000.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_quotes(
|
||||
&mut self,
|
||||
symbol: String,
|
||||
start_time: DateTime<Utc>,
|
||||
end_time: DateTime<Utc>,
|
||||
interval_ms: i64,
|
||||
) -> Vec<MarketUpdate> {
|
||||
let mut updates = Vec::new();
|
||||
let mut current_time = start_time;
|
||||
let mut price = self.base_price;
|
||||
|
||||
let price_dist = Normal::new(0.0, self.volatility).unwrap();
|
||||
let volume_dist = Normal::new(self.volume_mean, self.volume_std).unwrap();
|
||||
|
||||
while current_time <= end_time {
|
||||
// Generate price movement
|
||||
let return_pct = price_dist.sample(&mut self.rng) / 100.0;
|
||||
price *= 1.0 + return_pct;
|
||||
price = price.max(0.01); // Ensure positive price
|
||||
|
||||
// Calculate bid/ask
|
||||
let half_spread = price * self.spread_bps / 20000.0;
|
||||
let bid = price - half_spread;
|
||||
let ask = price + half_spread;
|
||||
|
||||
// Generate volume
|
||||
let volume = volume_dist.sample(&mut self.rng).max(0.0) as u32;
|
||||
|
||||
updates.push(MarketUpdate {
|
||||
symbol: symbol.clone(),
|
||||
timestamp: current_time,
|
||||
data: MarketDataType::Quote(Quote {
|
||||
bid,
|
||||
ask,
|
||||
bid_size: (volume / 10) as f64,
|
||||
ask_size: (volume / 10) as f64,
|
||||
}),
|
||||
});
|
||||
|
||||
current_time = current_time + Duration::milliseconds(interval_ms);
|
||||
}
|
||||
|
||||
updates
|
||||
}
|
||||
|
||||
pub fn generate_trades(
|
||||
&mut self,
|
||||
symbol: String,
|
||||
start_time: DateTime<Utc>,
|
||||
end_time: DateTime<Utc>,
|
||||
trades_per_minute: u32,
|
||||
) -> Vec<MarketUpdate> {
|
||||
let mut updates = Vec::new();
|
||||
let mut current_time = start_time;
|
||||
let mut price = self.base_price;
|
||||
|
||||
let price_dist = Normal::new(0.0, self.volatility / 100.0).unwrap();
|
||||
let volume_dist = Normal::new(100.0, 50.0).unwrap();
|
||||
|
||||
let interval_ms = 60_000 / trades_per_minute as i64;
|
||||
|
||||
while current_time <= end_time {
|
||||
// Generate price movement
|
||||
let return_pct = price_dist.sample(&mut self.rng);
|
||||
price *= 1.0 + return_pct;
|
||||
price = price.max(0.01);
|
||||
|
||||
// Generate trade size
|
||||
let raw_size: f64 = volume_dist.sample(&mut self.rng);
|
||||
let size = raw_size.max(1.0) as u32;
|
||||
|
||||
// Random buy/sell
|
||||
let is_buy = self.rng.gen_bool(0.5);
|
||||
|
||||
updates.push(MarketUpdate {
|
||||
symbol: symbol.clone(),
|
||||
timestamp: current_time,
|
||||
data: MarketDataType::Trade(Trade {
|
||||
price,
|
||||
size: size as f64,
|
||||
side: if is_buy { Side::Buy } else { Side::Sell },
|
||||
}),
|
||||
});
|
||||
|
||||
current_time = current_time + Duration::milliseconds(interval_ms);
|
||||
}
|
||||
|
||||
updates
|
||||
}
|
||||
|
||||
pub fn generate_bars(
|
||||
&mut self,
|
||||
symbol: String,
|
||||
start_time: DateTime<Utc>,
|
||||
end_time: DateTime<Utc>,
|
||||
timeframe: &str,
|
||||
) -> Vec<MarketUpdate> {
|
||||
let mut updates = Vec::new();
|
||||
let mut current_time = start_time;
|
||||
let mut price = self.base_price;
|
||||
|
||||
let interval = match timeframe {
|
||||
"1m" => Duration::minutes(1),
|
||||
"5m" => Duration::minutes(5),
|
||||
"15m" => Duration::minutes(15),
|
||||
"1h" => Duration::hours(1),
|
||||
"1d" => Duration::days(1),
|
||||
_ => Duration::minutes(1),
|
||||
};
|
||||
|
||||
let price_dist = Normal::new(0.0, self.volatility).unwrap();
|
||||
let volume_dist = Normal::new(self.volume_mean, self.volume_std).unwrap();
|
||||
|
||||
while current_time <= end_time {
|
||||
// Generate OHLC
|
||||
let open = price;
|
||||
let mut high = open;
|
||||
let mut low = open;
|
||||
|
||||
// Simulate intrabar movements
|
||||
for _ in 0..4 {
|
||||
let move_pct = price_dist.sample(&mut self.rng) / 100.0;
|
||||
price *= 1.0 + move_pct;
|
||||
price = price.max(0.01);
|
||||
high = high.max(price);
|
||||
low = low.min(price);
|
||||
}
|
||||
|
||||
let close = price;
|
||||
let volume = volume_dist.sample(&mut self.rng).max(0.0) as u64;
|
||||
|
||||
updates.push(MarketUpdate {
|
||||
symbol: symbol.clone(),
|
||||
timestamp: current_time,
|
||||
data: MarketDataType::Bar(Bar {
|
||||
open,
|
||||
high,
|
||||
low,
|
||||
close,
|
||||
volume: volume as f64,
|
||||
vwap: Some((open + high + low + close) / 4.0),
|
||||
}),
|
||||
});
|
||||
|
||||
current_time = current_time + interval;
|
||||
}
|
||||
|
||||
updates
|
||||
}
|
||||
|
||||
pub fn generate_mixed_data(
|
||||
&mut self,
|
||||
symbol: String,
|
||||
start_time: DateTime<Utc>,
|
||||
end_time: DateTime<Utc>,
|
||||
) -> Vec<MarketUpdate> {
|
||||
let mut all_updates = Vec::new();
|
||||
|
||||
// Generate quotes every 100ms
|
||||
let quotes = self.generate_quotes(
|
||||
symbol.clone(),
|
||||
start_time,
|
||||
end_time,
|
||||
100
|
||||
);
|
||||
all_updates.extend(quotes);
|
||||
|
||||
// Generate trades
|
||||
let trades = self.generate_trades(
|
||||
symbol.clone(),
|
||||
start_time,
|
||||
end_time,
|
||||
20 // 20 trades per minute
|
||||
);
|
||||
all_updates.extend(trades);
|
||||
|
||||
// Generate 1-minute bars
|
||||
let bars = self.generate_bars(
|
||||
symbol,
|
||||
start_time,
|
||||
end_time,
|
||||
"1m"
|
||||
);
|
||||
all_updates.extend(bars);
|
||||
|
||||
// Sort by timestamp
|
||||
all_updates.sort_by_key(|update| update.timestamp);
|
||||
|
||||
all_updates
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ pub mod time_providers;
|
|||
pub mod market_data_sources;
|
||||
pub mod execution_handlers;
|
||||
pub mod market_microstructure;
|
||||
pub mod mock_data_generator;
|
||||
|
||||
use crate::{MarketDataSource, ExecutionHandler, TimeProvider, TradingMode};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue