Tensors
Statistics
ML
Metrics
DataFrame
Time Series Stock Price Forecasting
A time series forecasting project for stock prices using synthetic market data, feature engineering, and regression baselines. It combines deepbox/ndarray, deepbox/stats, deepbox/ml, deepbox/metrics, deepbox/dataframe, deepbox/plot to deliver a larger production-style Deepbox workflow with reproducible outputs and documented architecture.
Features
- Data Generation: Synthetic stock price data with realistic patterns
- Feature Engineering: Technical indicators (MA, RSI, volatility)
- Statistical Analysis: Return distribution and correlation analysis
- Forecasting Models: Linear Regression and Ridge Regression baselines
- Evaluation: RMSE, MAE, directional accuracy
Deepbox Modules Used
deepbox/ndarraydeepbox/statsdeepbox/mldeepbox/metricsdeepbox/dataframedeepbox/plotProject Architecture
Source Files
index.ts
1/**2 * Time Series Stock Price Forecasting3 *4 * Demonstrates time series analysis and forecasting using Deepbox.5 *6 * Deepbox Modules Used:7 * - deepbox/ndarray: Tensor operations8 * - deepbox/stats: Statistical analysis9 * - deepbox/ml: Regression models10 * - deepbox/metrics: Forecasting metrics11 * - deepbox/dataframe: Data manipulation12 * - deepbox/plot: Visualization13 */1415import { existsSync, mkdirSync, writeFileSync } from "node:fs";16import { isNumericTypedArray, isTypedArray } from "deepbox/core";17import { DataFrame } from "deepbox/dataframe";18import { mae, mse, r2Score, rmse } from "deepbox/metrics";19import { LinearRegression, Ridge } from "deepbox/ml";20import { tensor } from "deepbox/ndarray";21import { Figure } from "deepbox/plot";22import { StandardScaler } from "deepbox/preprocess";23import { mean, pearsonr, std } from "deepbox/stats";2425// ============================================================================26// Configuration27// ============================================================================2829const OUTPUT_DIR = "docs/projects/04-stock-price-forecasting/output";30const NUM_DAYS = 500;31const LOOKBACK = 20;3233const expectNumericTypedArray = (34 value: unknown35): Float32Array | Float64Array | Int32Array | Uint8Array => {36 if (!isTypedArray(value) || !isNumericTypedArray(value)) {37 throw new Error("Expected numeric typed array");38 }39 return value;40};4142// ============================================================================43// Data Generation44// ============================================================================4546/**47 * Generate synthetic stock price data with realistic patterns48 */49function generateStockData(50 numDays: number,51 seed = 4252): {53 dates: string[];54 prices: number[];55 returns: number[];56 volume: number[];57} {58 let randomSeed = seed;59 const seededRandom = () => {60 randomSeed = (randomSeed * 1103515245 + 12345) & 0x7fffffff;61 return randomSeed / 0x7fffffff;62 };6364 const randomNormal = (mean: number, std: number) => {65 const u1 = seededRandom();66 const u2 = seededRandom();67 const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);68 return mean + std * z;69 };7071 const dates: string[] = [];72 const prices: number[] = [];73 const returns: number[] = [];74 const volume: number[] = [];7576 let price = 100; // Starting price77 const startDate = new Date("2023-01-01");7879 for (let i = 0; i < numDays; i++) {80 // Generate date81 const date = new Date(startDate);82 date.setDate(date.getDate() + i);83 dates.push(date.toISOString().split("T")[0]);8485 // Generate return with trend, volatility, and mean reversion86 const trend = 0.0002; // Slight upward trend87 const volatility = 0.02;88 const meanReversion = (-0.01 * (price - 100)) / 100; // Pull back to 1008990 const dailyReturn = trend + meanReversion + randomNormal(0, volatility);91 returns.push(dailyReturn);9293 // Update price94 price = price * (1 + dailyReturn);95 prices.push(price);9697 // Generate volume with some correlation to volatility98 const baseVolume = 1000000;99 const volumeMultiplier = 1 + Math.abs(dailyReturn) * 10;100 volume.push(Math.round(baseVolume * volumeMultiplier * (0.8 + seededRandom() * 0.4)));101 }102103 return { dates, prices, returns, volume };104}105106/**107 * Calculate technical indicators108 */109function calculateIndicators(110 prices: number[],111 returns: number[]112): {113 sma20: number[];114 sma50: number[];115 volatility20: number[];116 rsi14: number[];117 momentum10: number[];118} {119 const n = prices.length;120121 // Simple Moving Averages122 const sma20: number[] = [];123 const sma50: number[] = [];124 for (let i = 0; i < n; i++) {125 if (i >= 19) {126 const window = prices.slice(i - 19, i + 1);127 sma20.push(window.reduce((a, b) => a + b, 0) / 20);128 } else {129 sma20.push(NaN);130 }131 if (i >= 49) {132 const window = prices.slice(i - 49, i + 1);133 sma50.push(window.reduce((a, b) => a + b, 0) / 50);134 } else {135 sma50.push(NaN);136 }137 }138139 // Rolling Volatility (20-day)140 const volatility20: number[] = [];141 for (let i = 0; i < n; i++) {142 if (i >= 19) {143 const window = returns.slice(i - 19, i + 1);144 const mean = window.reduce((a, b) => a + b, 0) / 20;145 const variance = window.reduce((sum, r) => sum + (r - mean) ** 2, 0) / 20;146 volatility20.push(Math.sqrt(variance) * Math.sqrt(252)); // Annualized147 } else {148 volatility20.push(NaN);149 }150 }151152 // RSI (14-day)153 const rsi14: number[] = [];154 for (let i = 0; i < n; i++) {155 if (i >= 14) {156 const gains: number[] = [];157 const losses: number[] = [];158 for (let j = i - 13; j <= i; j++) {159 if (returns[j] > 0) {160 gains.push(returns[j]);161 losses.push(0);162 } else {163 gains.push(0);164 losses.push(-returns[j]);165 }166 }167 const avgGain = gains.reduce((a, b) => a + b, 0) / 14;168 const avgLoss = losses.reduce((a, b) => a + b, 0) / 14;169 const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;170 rsi14.push(100 - 100 / (1 + rs));171 } else {172 rsi14.push(NaN);173 }174 }175176 // Momentum (10-day price change)177 const momentum10: number[] = [];178 for (let i = 0; i < n; i++) {179 if (i >= 10) {180 momentum10.push((prices[i] - prices[i - 10]) / prices[i - 10]);181 } else {182 momentum10.push(NaN);183 }184 }185186 return { sma20, sma50, volatility20, rsi14, momentum10 };187}188189/**190 * Create features for forecasting191 */192function createFeatures(193 prices: number[],194 returns: number[],195 indicators: ReturnType<typeof calculateIndicators>,196 lookback: number197): { X: number[][]; y: number[]; validIndices: number[] } {198 const X: number[][] = [];199 const y: number[] = [];200 const validIndices: number[] = [];201202 const startIdx = Math.max(lookback, 50); // Ensure all indicators are available203204 for (let i = startIdx; i < prices.length - 1; i++) {205 // Check if all indicators are valid206 if (207 Number.isNaN(indicators.sma20[i]) ||208 Number.isNaN(indicators.sma50[i]) ||209 Number.isNaN(indicators.volatility20[i]) ||210 Number.isNaN(indicators.rsi14[i]) ||211 Number.isNaN(indicators.momentum10[i])212 ) {213 continue;214 }215216 const features: number[] = [];217218 // Lagged returns219 for (let j = 0; j < lookback; j++) {220 features.push(returns[i - j]);221 }222223 // Technical indicators224 features.push((prices[i] - indicators.sma20[i]) / indicators.sma20[i]); // Price vs SMA20225 features.push((prices[i] - indicators.sma50[i]) / indicators.sma50[i]); // Price vs SMA50226 features.push(indicators.volatility20[i]);227 features.push(indicators.rsi14[i] / 100); // Normalize RSI228 features.push(indicators.momentum10[i]);229230 X.push(features);231 y.push(returns[i + 1]); // Next day return232 validIndices.push(i);233 }234235 return { X, y, validIndices };236}237238// ============================================================================239// Main Execution240// ============================================================================241242console.log("═".repeat(70));243console.log(" TIME SERIES STOCK PRICE FORECASTING");244console.log(" Built with Deepbox — TypeScript toolkit for AI & numerical computing");245console.log("═".repeat(70));246247// Create output directory248if (!existsSync(OUTPUT_DIR)) {249 mkdirSync(OUTPUT_DIR, { recursive: true });250}251252// ============================================================================253// Step 1: Generate Data254// ============================================================================255256console.log("\n📊 STEP 1: Generating Stock Price Data");257console.log("─".repeat(70));258259const { dates, prices, returns, volume: _volume } = generateStockData(NUM_DAYS);260261console.log(`\n✓ Generated ${NUM_DAYS} days of synthetic stock data`);262console.log(` Date Range: ${dates[0]} to ${dates[dates.length - 1]}`);263console.log(` Starting Price: $${prices[0].toFixed(2)}`);264console.log(` Ending Price: $${prices[prices.length - 1].toFixed(2)}`);265console.log(` Total Return: ${((prices[prices.length - 1] / prices[0] - 1) * 100).toFixed(2)}%`);266267// Basic statistics268const returnsTensor = tensor(returns);269const meanReturn = Number(mean(returnsTensor).data[0]);270const stdReturn = Number(std(returnsTensor).data[0]);271272console.log(`\nReturn Statistics:`);273console.log(` Mean Daily Return: ${(meanReturn * 100).toFixed(4)}%`);274console.log(` Daily Volatility: ${(stdReturn * 100).toFixed(4)}%`);275console.log(` Annualized Return: ${(meanReturn * 252 * 100).toFixed(2)}%`);276console.log(` Annualized Vol: ${(stdReturn * Math.sqrt(252) * 100).toFixed(2)}%`);277278// ============================================================================279// Step 2: Calculate Technical Indicators280// ============================================================================281282console.log("\n📈 STEP 2: Calculating Technical Indicators");283console.log("─".repeat(70));284285const indicators = calculateIndicators(prices, returns);286287// Show sample of indicators288const sampleIdx = prices.length - 1;289console.log(`\nLatest Indicators (${dates[sampleIdx]}):`);290console.log(` Price: $${prices[sampleIdx].toFixed(2)}`);291console.log(` SMA(20): $${indicators.sma20[sampleIdx].toFixed(2)}`);292console.log(` SMA(50): $${indicators.sma50[sampleIdx].toFixed(2)}`);293console.log(` Volatility: ${(indicators.volatility20[sampleIdx] * 100).toFixed(2)}%`);294console.log(` RSI(14): ${indicators.rsi14[sampleIdx].toFixed(2)}`);295console.log(` Momentum: ${(indicators.momentum10[sampleIdx] * 100).toFixed(2)}%`);296297// ============================================================================298// Step 3: Feature Engineering299// ============================================================================300301console.log("\n🔧 STEP 3: Feature Engineering");302console.log("─".repeat(70));303304const { X, y, validIndices: _validIndices } = createFeatures(prices, returns, indicators, LOOKBACK);305306console.log(`\n✓ Created feature matrix`);307console.log(` Samples: ${X.length}`);308console.log(` Features per sample: ${X[0].length}`);309console.log(` Feature breakdown:`);310console.log(` - ${LOOKBACK} lagged returns`);311console.log(` - 5 technical indicators`);312313// ============================================================================314// Step 4: Train/Test Split315// ============================================================================316317console.log("\n📦 STEP 4: Train/Test Split");318console.log("─".repeat(70));319320const splitIdx = Math.floor(X.length * 0.8);321const XTrain = X.slice(0, splitIdx);322const XTest = X.slice(splitIdx);323const yTrain = y.slice(0, splitIdx);324const yTest = y.slice(splitIdx);325326console.log(`\n✓ Time-based split (no shuffle to preserve temporal order)`);327console.log(` Training: ${XTrain.length} samples`);328console.log(` Testing: ${XTest.length} samples`);329330// Scale features331const scaler = new StandardScaler();332scaler.fit(tensor(XTrain));333const XTrainScaled = scaler.transform(tensor(XTrain));334const XTestScaled = scaler.transform(tensor(XTest));335336console.log(`✓ Applied StandardScaler`);337338// ============================================================================339// Step 5: Model Training340// ============================================================================341342console.log("\n🤖 STEP 5: Model Training");343console.log("─".repeat(70));344345// Linear Regression346console.log("\nTraining Linear Regression...");347const lr = new LinearRegression();348lr.fit(XTrainScaled, tensor(yTrain));349const yPredLR = lr.predict(XTestScaled);350351// Ridge Regression352console.log("Training Ridge Regression (alpha=0.1)...");353const ridge = new Ridge({ alpha: 0.1 });354ridge.fit(XTrainScaled, tensor(yTrain));355const yPredRidge = ridge.predict(XTestScaled);356357// Baseline: Predict mean358const meanPred = yTrain.reduce((a, b) => a + b, 0) / yTrain.length;359const yPredBaseline = tensor(Array(yTest.length).fill(meanPred));360361console.log("✓ Models trained");362363// ============================================================================364// Step 6: Model Evaluation365// ============================================================================366367console.log("\n📊 STEP 6: Model Evaluation");368console.log("─".repeat(70));369370const yTestTensor = tensor(yTest);371372// Calculate metrics373const results = [374 {375 name: "Mean Baseline",376 mse: mse(yTestTensor, yPredBaseline),377 rmse: rmse(yTestTensor, yPredBaseline),378 mae: mae(yTestTensor, yPredBaseline),379 r2: r2Score(yTestTensor, yPredBaseline),380 pred: yPredBaseline,381 },382 {383 name: "Linear Regression",384 mse: mse(yTestTensor, yPredLR),385 rmse: rmse(yTestTensor, yPredLR),386 mae: mae(yTestTensor, yPredLR),387 r2: r2Score(yTestTensor, yPredLR),388 pred: yPredLR,389 },390 {391 name: "Ridge Regression",392 mse: mse(yTestTensor, yPredRidge),393 rmse: rmse(yTestTensor, yPredRidge),394 mae: mae(yTestTensor, yPredRidge),395 r2: r2Score(yTestTensor, yPredRidge),396 pred: yPredRidge,397 },398];399400console.log("\nModel Comparison:\n");401const metricsDF = new DataFrame({402 Model: results.map((r) => r.name),403 "MSE (×10⁻⁵)": results.map((r) => (Number(r.mse) * 100000).toFixed(4)),404 "RMSE (%)": results.map((r) => (Number(r.rmse) * 100).toFixed(4)),405 "MAE (%)": results.map((r) => (Number(r.mae) * 100).toFixed(4)),406 "R² Score": results.map((r) => Number(r.r2).toFixed(4)),407});408console.log(metricsDF.toString());409410// Directional accuracy411console.log("\nDirectional Accuracy (predicting up/down):");412for (const result of results) {413 const predData = expectNumericTypedArray(result.pred.data);414 let correct = 0;415 for (let i = 0; i < yTest.length; i++) {416 const actualDirection = yTest[i] > 0 ? 1 : -1;417 const predDirection = predData[i] > 0 ? 1 : -1;418 if (actualDirection === predDirection) correct++;419 }420 const dirAcc = correct / yTest.length;421 console.log(` ${result.name.padEnd(20)}: ${(dirAcc * 100).toFixed(2)}%`);422}423424// ============================================================================425// Step 7: Autocorrelation Analysis426// ============================================================================427428console.log("\n🔍 STEP 7: Autocorrelation Analysis");429console.log("─".repeat(70));430431// Calculate autocorrelation of returns432console.log("\nReturn Autocorrelation:");433for (const lag of [1, 5, 10, 20]) {434 const returns1 = returns.slice(0, returns.length - lag);435 const returns2 = returns.slice(lag);436 const [corr] = pearsonr(tensor(returns1), tensor(returns2));437 console.log(` Lag ${String(lag).padStart(2)}: ${corr.toFixed(4)}`);438}439440// ============================================================================441// Step 8: Visualizations442// ============================================================================443444console.log("\n📊 STEP 8: Generating Visualizations");445console.log("─".repeat(70));446447// Price chart448try {449 const fig = new Figure({ width: 1000, height: 400 });450 const ax = fig.addAxes();451452 const xValues = Array.from({ length: prices.length }, (_, i) => i);453 ax.plot(tensor(xValues), tensor(prices), { color: "#2196F3", linewidth: 1 });454 ax.setTitle("Stock Price Over Time");455 ax.setXLabel("Day");456 ax.setYLabel("Price ($)");457458 const svg = fig.renderSVG();459 writeFileSync(`${OUTPUT_DIR}/price-chart.svg`, svg.svg);460 console.log(` ✓ Saved: ${OUTPUT_DIR}/price-chart.svg`);461} catch (e) {462 console.log(` ⚠ Could not generate price chart: ${e}`);463}464465// Returns distribution466try {467 const fig = new Figure({ width: 800, height: 400 });468 const ax = fig.addAxes();469470 // Create histogram471 const numBins = 30;472 const minRet = Math.min(...returns);473 const maxRet = Math.max(...returns);474 const binWidth = (maxRet - minRet) / numBins;475476 const bins: number[] = [];477 const counts: number[] = [];478 for (let i = 0; i < numBins; i++) {479 const binCenter = minRet + (i + 0.5) * binWidth;480 const count = returns.filter(481 (r) => r >= minRet + i * binWidth && r < minRet + (i + 1) * binWidth482 ).length;483 bins.push(binCenter * 100);484 counts.push(count);485 }486487 ax.bar(tensor(bins), tensor(counts), { color: "#4CAF50" });488 ax.setTitle("Daily Returns Distribution");489 ax.setXLabel("Return (%)");490 ax.setYLabel("Frequency");491492 const svg = fig.renderSVG();493 writeFileSync(`${OUTPUT_DIR}/returns-distribution.svg`, svg.svg);494 console.log(` ✓ Saved: ${OUTPUT_DIR}/returns-distribution.svg`);495} catch (e) {496 console.log(` ⚠ Could not generate returns distribution: ${e}`);497}498499// ============================================================================500// Step 9: Summary501// ============================================================================502503console.log(`\n${"═".repeat(70)}`);504console.log(" FORECASTING COMPLETE - SUMMARY");505console.log("═".repeat(70));506507const bestModel = results.reduce((best, r) => (Number(r.r2) > Number(best.r2) ? r : best));508509console.log("\n📌 Key Findings:\n");510console.log(" 1. Data Overview:");511console.log(` • ${NUM_DAYS} days of price data`);512console.log(` • Annualized return: ${(meanReturn * 252 * 100).toFixed(2)}%`);513console.log(` • Annualized volatility: ${(stdReturn * Math.sqrt(252) * 100).toFixed(2)}%`);514515console.log("\n 2. Best Model:");516console.log(` • ${bestModel.name}`);517console.log(` • R² Score: ${Number(bestModel.r2).toFixed(4)}`);518console.log(` • RMSE: ${(Number(bestModel.rmse) * 100).toFixed(4)}%`);519520console.log("\n 3. Observations:");521console.log(" • Stock returns show low autocorrelation (efficient market)");522console.log(" • Technical indicators provide marginal improvement");523console.log(" • Directional prediction is challenging (~50% baseline)");524525console.log("\n📁 Output Files:");526console.log(` • ${OUTPUT_DIR}/price-chart.svg`);527console.log(` • ${OUTPUT_DIR}/returns-distribution.svg`);528529console.log(`\n${"═".repeat(70)}`);530console.log(" ✅ Stock Price Forecasting Complete!");531console.log("═".repeat(70));532Console Output
$ npx tsx 04-stock-price-forecasting/index.ts
See the example README for the expected console and artifact output.Key Takeaways
- Data Generation: Synthetic stock price data with realistic patterns
- Feature Engineering: Technical indicators (MA, RSI, volatility)
- Statistical Analysis: Return distribution and correlation analysis
- Forecasting Models: Linear Regression and Ridge Regression baselines
- Use deepbox/ndarray for Tensor operations, time series processing.
- Use deepbox/stats for Statistical tests, correlation analysis.