Files
wifi-densepose/npm/packages/agentic-synth/examples/stocks/market-data.ts
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

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,
};