added rust engine and adapter pattern

This commit is contained in:
Boki 2025-07-03 22:28:31 -04:00
parent a58072cf93
commit 0a4702d12a
6 changed files with 328 additions and 186 deletions

View file

@ -0,0 +1,135 @@
import { MarketData, Signal } from '../types';
export interface IStrategyExecutor {
onMarketData(data: MarketData): Signal[];
onFill(symbol: string, quantity: number, price: number, side: string): void;
getState(): any;
setState(state: any): void;
}
/**
* Executes strategies in-process for backtesting
* This avoids the complexity of async callbacks between Rust and TypeScript
*/
export class StrategyExecutor implements IStrategyExecutor {
private strategies: Map<string, any> = new Map();
private strategyStates: Map<string, any> = new Map();
registerStrategy(id: string, strategy: any) {
this.strategies.set(id, strategy);
this.strategyStates.set(id, {
priceHistory: new Map<string, number[]>(),
positions: new Map<string, number>(),
});
}
onMarketData(data: MarketData): Signal[] {
const allSignals: Signal[] = [];
for (const [id, strategy] of this.strategies) {
const state = this.strategyStates.get(id)!;
const signals = strategy.onMarketData(data, state);
if (signals && signals.length > 0) {
allSignals.push(...signals);
}
}
return allSignals;
}
onFill(symbol: string, quantity: number, price: number, side: string): void {
for (const [id, strategy] of this.strategies) {
const state = this.strategyStates.get(id)!;
if (strategy.onFill) {
strategy.onFill({ symbol, quantity, price, side }, state);
}
// Update position tracking
const currentPos = state.positions.get(symbol) || 0;
const newPos = side === 'buy' ? currentPos + quantity : currentPos - quantity;
if (Math.abs(newPos) < 0.0001) {
state.positions.delete(symbol);
} else {
state.positions.set(symbol, newPos);
}
}
}
getState(): any {
return Object.fromEntries(this.strategyStates);
}
setState(state: any): void {
this.strategyStates = new Map(Object.entries(state));
}
}
// Example SMA Crossover Strategy
export const SMACrossoverStrategy = {
onMarketData(data: MarketData, state: any): Signal[] {
const signals: Signal[] = [];
// Check if it's bar data
if (data.type !== 'bar') return signals;
const { symbol, close } = data.data;
const fastPeriod = 5;
const slowPeriod = 15;
// Update price history
if (!state.priceHistory.has(symbol)) {
state.priceHistory.set(symbol, []);
}
const history = state.priceHistory.get(symbol)!;
history.push(close);
// Keep only necessary history
if (history.length > slowPeriod + 1) {
history.shift();
}
// Need enough data
if (history.length >= slowPeriod) {
// Calculate SMAs
const fastSMA = history.slice(-fastPeriod).reduce((a, b) => a + b, 0) / fastPeriod;
const slowSMA = history.reduce((a, b) => a + b, 0) / history.length;
// Previous SMAs (if we have enough history)
if (history.length > slowPeriod) {
const prevHistory = history.slice(0, -1);
const prevFastSMA = prevHistory.slice(-fastPeriod).reduce((a, b) => a + b, 0) / fastPeriod;
const prevSlowSMA = prevHistory.reduce((a, b) => a + b, 0) / prevHistory.length;
const currentPosition = state.positions.get(symbol) || 0;
// Golden cross - buy signal
if (prevFastSMA <= prevSlowSMA && fastSMA > slowSMA && currentPosition <= 0) {
signals.push({
symbol,
signal_type: 'Buy',
strength: 1.0,
quantity: 100,
reason: 'Golden cross',
});
}
// Death cross - sell signal
else if (prevFastSMA >= prevSlowSMA && fastSMA < slowSMA && currentPosition >= 0) {
signals.push({
symbol,
signal_type: 'Sell',
strength: 1.0,
quantity: 100,
reason: 'Death cross',
});
}
}
}
return signals;
},
};