git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
544 lines
15 KiB
TypeScript
544 lines
15 KiB
TypeScript
/**
|
|
* Stock Market Data Generation Examples
|
|
*
|
|
* Demonstrates realistic OHLCV data generation, technical indicators,
|
|
* multi-timeframe data, market depth, and tick-by-tick simulation.
|
|
*/
|
|
|
|
import { AgenticSynth } from '../../../src';
|
|
|
|
// ============================================================================
|
|
// OHLCV Data Generation
|
|
// ============================================================================
|
|
|
|
interface OHLCVBar {
|
|
timestamp: Date;
|
|
open: number;
|
|
high: number;
|
|
low: number;
|
|
close: number;
|
|
volume: number;
|
|
symbol: string;
|
|
}
|
|
|
|
/**
|
|
* Generate realistic OHLCV (candlestick) data with proper market microstructure
|
|
*/
|
|
async function generateOHLCVData() {
|
|
const synth = new AgenticSynth();
|
|
|
|
const ohlcvData = await synth.generate<OHLCVBar>({
|
|
count: 390, // One trading day (6.5 hours * 60 minutes)
|
|
template: {
|
|
timestamp: '{{date.recent}}',
|
|
open: '{{number.float(100, 200, 2)}}',
|
|
high: '{{number.float(100, 200, 2)}}',
|
|
low: '{{number.float(100, 200, 2)}}',
|
|
close: '{{number.float(100, 200, 2)}}',
|
|
volume: '{{number.int(100000, 10000000)}}',
|
|
symbol: 'AAPL',
|
|
},
|
|
constraints: [
|
|
// High must be >= max(open, close)
|
|
'bar.high >= Math.max(bar.open, bar.close)',
|
|
// Low must be <= min(open, close)
|
|
'bar.low <= Math.min(bar.open, bar.close)',
|
|
// Volume must be positive
|
|
'bar.volume > 0',
|
|
],
|
|
relationships: [
|
|
{
|
|
type: 'temporal',
|
|
field: 'timestamp',
|
|
interval: '1m', // 1-minute bars
|
|
},
|
|
{
|
|
type: 'continuity',
|
|
sourceField: 'close',
|
|
targetField: 'open',
|
|
description: 'Next bar opens at previous close',
|
|
},
|
|
],
|
|
});
|
|
|
|
// Post-process to ensure OHLCV validity
|
|
const validatedData = ohlcvData.map((bar, idx) => {
|
|
if (idx > 0) {
|
|
bar.open = ohlcvData[idx - 1].close; // Open = previous close
|
|
}
|
|
bar.high = Math.max(bar.open, bar.close, bar.high);
|
|
bar.low = Math.min(bar.open, bar.close, bar.low);
|
|
return bar;
|
|
});
|
|
|
|
console.log('Generated OHLCV Data (first 5 bars):');
|
|
console.log(validatedData.slice(0, 5));
|
|
|
|
return validatedData;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Technical Indicators
|
|
// ============================================================================
|
|
|
|
interface TechnicalIndicators {
|
|
timestamp: Date;
|
|
price: number;
|
|
sma_20: number;
|
|
sma_50: number;
|
|
rsi_14: number;
|
|
macd: number;
|
|
macd_signal: number;
|
|
bb_upper: number;
|
|
bb_middle: number;
|
|
bb_lower: number;
|
|
volume: number;
|
|
symbol: string;
|
|
}
|
|
|
|
/**
|
|
* Generate price data with technical indicators pre-calculated
|
|
*/
|
|
async function generateTechnicalIndicators() {
|
|
const synth = new AgenticSynth();
|
|
|
|
// First generate base price series
|
|
const priceData = await synth.generate<{ price: number; volume: number }>({
|
|
count: 100,
|
|
template: {
|
|
price: '{{number.float(150, 160, 2)}}',
|
|
volume: '{{number.int(1000000, 5000000)}}',
|
|
},
|
|
});
|
|
|
|
// Calculate indicators (simplified for demonstration)
|
|
const calculateSMA = (data: number[], period: number): number[] => {
|
|
const sma: number[] = [];
|
|
for (let i = 0; i < data.length; i++) {
|
|
if (i < period - 1) {
|
|
sma.push(data[i]);
|
|
} else {
|
|
const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
|
|
sma.push(sum / period);
|
|
}
|
|
}
|
|
return sma;
|
|
};
|
|
|
|
const calculateRSI = (data: number[], period: number = 14): number[] => {
|
|
const rsi: number[] = [];
|
|
for (let i = 0; i < data.length; i++) {
|
|
if (i < period) {
|
|
rsi.push(50); // Neutral RSI for initial period
|
|
} else {
|
|
const gains: number[] = [];
|
|
const losses: number[] = [];
|
|
for (let j = i - period + 1; j <= i; j++) {
|
|
const change = data[j] - data[j - 1];
|
|
if (change > 0) gains.push(change);
|
|
else losses.push(Math.abs(change));
|
|
}
|
|
const avgGain = gains.reduce((a, b) => a + b, 0) / period;
|
|
const avgLoss = losses.reduce((a, b) => a + b, 0) / period;
|
|
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
rsi.push(100 - 100 / (1 + rs));
|
|
}
|
|
}
|
|
return rsi;
|
|
};
|
|
|
|
const prices = priceData.map((d) => d.price);
|
|
const sma20 = calculateSMA(prices, 20);
|
|
const sma50 = calculateSMA(prices, 50);
|
|
const rsi = calculateRSI(prices);
|
|
|
|
const technicalData: TechnicalIndicators[] = priceData.map((bar, idx) => ({
|
|
timestamp: new Date(Date.now() - (priceData.length - idx) * 60000),
|
|
price: bar.price,
|
|
sma_20: Number(sma20[idx].toFixed(2)),
|
|
sma_50: Number(sma50[idx].toFixed(2)),
|
|
rsi_14: Number(rsi[idx].toFixed(2)),
|
|
macd: Number((sma20[idx] - sma50[idx]).toFixed(2)),
|
|
macd_signal: Number((sma20[idx] - sma50[idx]) * 0.9).toFixed(2), // Simplified
|
|
bb_upper: Number((sma20[idx] * 1.02).toFixed(2)),
|
|
bb_middle: Number(sma20[idx].toFixed(2)),
|
|
bb_lower: Number((sma20[idx] * 0.98).toFixed(2)),
|
|
volume: bar.volume,
|
|
symbol: 'AAPL',
|
|
}));
|
|
|
|
console.log('Technical Indicators (last 5 bars):');
|
|
console.log(technicalData.slice(-5));
|
|
|
|
return technicalData;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Multi-Timeframe Data
|
|
// ============================================================================
|
|
|
|
interface MultiTimeframeData {
|
|
'1m': OHLCVBar[];
|
|
'5m': OHLCVBar[];
|
|
'1h': OHLCVBar[];
|
|
'1d': OHLCVBar[];
|
|
}
|
|
|
|
/**
|
|
* Generate data across multiple timeframes with proper aggregation
|
|
*/
|
|
async function generateMultiTimeframeData(): Promise<MultiTimeframeData> {
|
|
const synth = new AgenticSynth();
|
|
|
|
// Generate 1-minute bars (base timeframe)
|
|
const bars1m = await synth.generate<OHLCVBar>({
|
|
count: 1560, // 4 trading days worth of 1-minute data
|
|
template: {
|
|
timestamp: '{{date.recent}}',
|
|
open: '{{number.float(100, 200, 2)}}',
|
|
high: '{{number.float(100, 200, 2)}}',
|
|
low: '{{number.float(100, 200, 2)}}',
|
|
close: '{{number.float(100, 200, 2)}}',
|
|
volume: '{{number.int(10000, 100000)}}',
|
|
symbol: 'AAPL',
|
|
},
|
|
});
|
|
|
|
// Aggregate to 5-minute bars
|
|
const bars5m: OHLCVBar[] = [];
|
|
for (let i = 0; i < bars1m.length; i += 5) {
|
|
const chunk = bars1m.slice(i, i + 5);
|
|
if (chunk.length === 5) {
|
|
bars5m.push({
|
|
timestamp: chunk[0].timestamp,
|
|
open: chunk[0].open,
|
|
high: Math.max(...chunk.map((b) => b.high)),
|
|
low: Math.min(...chunk.map((b) => b.low)),
|
|
close: chunk[4].close,
|
|
volume: chunk.reduce((sum, b) => sum + b.volume, 0),
|
|
symbol: 'AAPL',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Aggregate to 1-hour bars
|
|
const bars1h: OHLCVBar[] = [];
|
|
for (let i = 0; i < bars1m.length; i += 60) {
|
|
const chunk = bars1m.slice(i, i + 60);
|
|
if (chunk.length === 60) {
|
|
bars1h.push({
|
|
timestamp: chunk[0].timestamp,
|
|
open: chunk[0].open,
|
|
high: Math.max(...chunk.map((b) => b.high)),
|
|
low: Math.min(...chunk.map((b) => b.low)),
|
|
close: chunk[59].close,
|
|
volume: chunk.reduce((sum, b) => sum + b.volume, 0),
|
|
symbol: 'AAPL',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Aggregate to 1-day bars
|
|
const bars1d: OHLCVBar[] = [];
|
|
for (let i = 0; i < bars1m.length; i += 390) {
|
|
const chunk = bars1m.slice(i, i + 390);
|
|
if (chunk.length === 390) {
|
|
bars1d.push({
|
|
timestamp: chunk[0].timestamp,
|
|
open: chunk[0].open,
|
|
high: Math.max(...chunk.map((b) => b.high)),
|
|
low: Math.min(...chunk.map((b) => b.low)),
|
|
close: chunk[389].close,
|
|
volume: chunk.reduce((sum, b) => sum + b.volume, 0),
|
|
symbol: 'AAPL',
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log('Multi-timeframe data generated:');
|
|
console.log(`1m bars: ${bars1m.length}`);
|
|
console.log(`5m bars: ${bars5m.length}`);
|
|
console.log(`1h bars: ${bars1h.length}`);
|
|
console.log(`1d bars: ${bars1d.length}`);
|
|
|
|
return {
|
|
'1m': bars1m,
|
|
'5m': bars5m,
|
|
'1h': bars1h,
|
|
'1d': bars1d,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Market Depth Data (Order Book)
|
|
// ============================================================================
|
|
|
|
interface OrderBookLevel {
|
|
price: number;
|
|
size: number;
|
|
orders: number;
|
|
}
|
|
|
|
interface MarketDepth {
|
|
timestamp: Date;
|
|
symbol: string;
|
|
bids: OrderBookLevel[];
|
|
asks: OrderBookLevel[];
|
|
spread: number;
|
|
midPrice: number;
|
|
}
|
|
|
|
/**
|
|
* Generate realistic Level 2 market depth data (order book)
|
|
*/
|
|
async function generateMarketDepth() {
|
|
const synth = new AgenticSynth();
|
|
|
|
const midPrice = 150.0;
|
|
const tickSize = 0.01;
|
|
const depth = 20; // 20 levels on each side
|
|
|
|
const marketDepth = await synth.generate<MarketDepth>({
|
|
count: 100,
|
|
template: {
|
|
timestamp: '{{date.recent}}',
|
|
symbol: 'AAPL',
|
|
bids: [],
|
|
asks: [],
|
|
spread: 0,
|
|
midPrice: midPrice,
|
|
},
|
|
});
|
|
|
|
// Generate order book levels
|
|
const enrichedDepth = marketDepth.map((snapshot) => {
|
|
const bids: OrderBookLevel[] = [];
|
|
const asks: OrderBookLevel[] = [];
|
|
|
|
// Generate bid side (below mid price)
|
|
for (let i = 0; i < depth; i++) {
|
|
bids.push({
|
|
price: Number((midPrice - i * tickSize).toFixed(2)),
|
|
size: Math.floor(Math.random() * 10000) + 100,
|
|
orders: Math.floor(Math.random() * 50) + 1,
|
|
});
|
|
}
|
|
|
|
// Generate ask side (above mid price)
|
|
for (let i = 0; i < depth; i++) {
|
|
asks.push({
|
|
price: Number((midPrice + (i + 1) * tickSize).toFixed(2)),
|
|
size: Math.floor(Math.random() * 10000) + 100,
|
|
orders: Math.floor(Math.random() * 50) + 1,
|
|
});
|
|
}
|
|
|
|
const bestBid = bids[0].price;
|
|
const bestAsk = asks[0].price;
|
|
|
|
return {
|
|
...snapshot,
|
|
bids,
|
|
asks,
|
|
spread: Number((bestAsk - bestBid).toFixed(2)),
|
|
midPrice: Number(((bestBid + bestAsk) / 2).toFixed(2)),
|
|
};
|
|
});
|
|
|
|
console.log('Market Depth (first snapshot):');
|
|
console.log({
|
|
timestamp: enrichedDepth[0].timestamp,
|
|
bestBid: enrichedDepth[0].bids[0],
|
|
bestAsk: enrichedDepth[0].asks[0],
|
|
spread: enrichedDepth[0].spread,
|
|
totalBidVolume: enrichedDepth[0].bids.reduce((sum, b) => sum + b.size, 0),
|
|
totalAskVolume: enrichedDepth[0].asks.reduce((sum, a) => sum + a.size, 0),
|
|
});
|
|
|
|
return enrichedDepth;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tick-by-Tick Data
|
|
// ============================================================================
|
|
|
|
interface Tick {
|
|
timestamp: Date;
|
|
symbol: string;
|
|
price: number;
|
|
size: number;
|
|
side: 'buy' | 'sell';
|
|
exchange: string;
|
|
conditions: string[];
|
|
}
|
|
|
|
/**
|
|
* Generate high-frequency tick-by-tick trade data
|
|
*/
|
|
async function generateTickData() {
|
|
const synth = new AgenticSynth();
|
|
|
|
const tickData = await synth.generate<Tick>({
|
|
count: 10000, // 10k ticks (typical for a few minutes of active trading)
|
|
template: {
|
|
timestamp: '{{date.recent}}',
|
|
symbol: 'AAPL',
|
|
price: '{{number.float(149.5, 150.5, 2)}}',
|
|
size: '{{number.int(1, 1000)}}',
|
|
side: '{{random.arrayElement(["buy", "sell"])}}',
|
|
exchange: '{{random.arrayElement(["NASDAQ", "NYSE", "BATS", "IEX"])}}',
|
|
conditions: [],
|
|
},
|
|
constraints: [
|
|
'tick.size > 0',
|
|
'tick.price > 0',
|
|
],
|
|
relationships: [
|
|
{
|
|
type: 'temporal',
|
|
field: 'timestamp',
|
|
interval: '10ms', // High-frequency ticks
|
|
},
|
|
],
|
|
});
|
|
|
|
// Add trade conditions (regulatory tags)
|
|
const enrichedTicks = tickData.map((tick) => {
|
|
const conditions: string[] = [];
|
|
|
|
if (tick.size >= 100) conditions.push('BLOCK');
|
|
if (tick.size >= 10000) conditions.push('INSTITUTIONAL');
|
|
if (Math.random() < 0.05) conditions.push('ODD_LOT');
|
|
if (Math.random() < 0.1) conditions.push('EXTENDED_HOURS');
|
|
|
|
return {
|
|
...tick,
|
|
conditions,
|
|
};
|
|
});
|
|
|
|
// Calculate tick statistics
|
|
const buyTicks = enrichedTicks.filter((t) => t.side === 'buy');
|
|
const sellTicks = enrichedTicks.filter((t) => t.side === 'sell');
|
|
const avgBuyPrice =
|
|
buyTicks.reduce((sum, t) => sum + t.price, 0) / buyTicks.length;
|
|
const avgSellPrice =
|
|
sellTicks.reduce((sum, t) => sum + t.price, 0) / sellTicks.length;
|
|
|
|
console.log('Tick Data Statistics:');
|
|
console.log({
|
|
totalTicks: enrichedTicks.length,
|
|
buyTicks: buyTicks.length,
|
|
sellTicks: sellTicks.length,
|
|
avgBuyPrice: avgBuyPrice.toFixed(2),
|
|
avgSellPrice: avgSellPrice.toFixed(2),
|
|
priceImbalance: (avgBuyPrice - avgSellPrice).toFixed(4),
|
|
avgTradeSize:
|
|
enrichedTicks.reduce((sum, t) => sum + t.size, 0) / enrichedTicks.length,
|
|
});
|
|
|
|
return enrichedTicks;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Market Microstructure Patterns
|
|
// ============================================================================
|
|
|
|
interface MicrostructureMetrics {
|
|
timestamp: Date;
|
|
symbol: string;
|
|
effectiveSpread: number;
|
|
realizedSpread: number;
|
|
priceImpact: number;
|
|
toxicity: number; // Adverse selection measure
|
|
orderImbalance: number;
|
|
volatility: number;
|
|
}
|
|
|
|
/**
|
|
* Generate market microstructure metrics for analysis
|
|
*/
|
|
async function generateMicrostructureMetrics() {
|
|
const synth = new AgenticSynth();
|
|
|
|
const metrics = await synth.generate<MicrostructureMetrics>({
|
|
count: 390, // One trading day
|
|
template: {
|
|
timestamp: '{{date.recent}}',
|
|
symbol: 'AAPL',
|
|
effectiveSpread: '{{number.float(0.01, 0.05, 4)}}',
|
|
realizedSpread: '{{number.float(0.005, 0.03, 4)}}',
|
|
priceImpact: '{{number.float(0.001, 0.02, 4)}}',
|
|
toxicity: '{{number.float(0, 1, 4)}}',
|
|
orderImbalance: '{{number.float(-1, 1, 4)}}',
|
|
volatility: '{{number.float(0.01, 0.05, 4)}}',
|
|
},
|
|
constraints: [
|
|
'metrics.effectiveSpread >= metrics.realizedSpread',
|
|
'metrics.toxicity >= 0 && metrics.toxicity <= 1',
|
|
'metrics.orderImbalance >= -1 && metrics.orderImbalance <= 1',
|
|
],
|
|
});
|
|
|
|
console.log('Microstructure Metrics (sample):');
|
|
console.log(metrics.slice(0, 5));
|
|
|
|
return metrics;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Main Execution
|
|
// ============================================================================
|
|
|
|
async function main() {
|
|
console.log('='.repeat(80));
|
|
console.log('Stock Market Data Generation Examples');
|
|
console.log('='.repeat(80));
|
|
console.log();
|
|
|
|
try {
|
|
console.log('1. Generating OHLCV Data...');
|
|
await generateOHLCVData();
|
|
console.log();
|
|
|
|
console.log('2. Generating Technical Indicators...');
|
|
await generateTechnicalIndicators();
|
|
console.log();
|
|
|
|
console.log('3. Generating Multi-Timeframe Data...');
|
|
await generateMultiTimeframeData();
|
|
console.log();
|
|
|
|
console.log('4. Generating Market Depth...');
|
|
await generateMarketDepth();
|
|
console.log();
|
|
|
|
console.log('5. Generating Tick Data...');
|
|
await generateTickData();
|
|
console.log();
|
|
|
|
console.log('6. Generating Microstructure Metrics...');
|
|
await generateMicrostructureMetrics();
|
|
console.log();
|
|
|
|
console.log('All examples completed successfully!');
|
|
} catch (error) {
|
|
console.error('Error generating market data:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
export {
|
|
generateOHLCVData,
|
|
generateTechnicalIndicators,
|
|
generateMultiTimeframeData,
|
|
generateMarketDepth,
|
|
generateTickData,
|
|
generateMicrostructureMetrics,
|
|
};
|