This commit is contained in:
Boki 2025-06-11 10:35:15 -04:00
parent d85cd58acd
commit 597c6efc9b
91 changed files with 2224 additions and 1400 deletions

81
.eslintignore Normal file
View file

@ -0,0 +1,81 @@
# Dependencies
node_modules/
**/node_modules/
# Build outputs
dist/
build/
**/dist/
**/build/
.next/
**/.next/
# Cache directories
.turbo/
**/.turbo/
.cache/
**/.cache/
# Environment files
.env
.env.local
.env.production
.env.staging
**/.env*
# Lock files
package-lock.json
yarn.lock
bun.lockb
pnpm-lock.yaml
# Logs
*.log
logs/
**/logs/
# Database files
*.db
*.sqlite
*.sqlite3
# Temporary files
*.tmp
*.temp
.DS_Store
Thumbs.db
# Generated files
*.d.ts
**/*.d.ts
# JavaScript files (we're focusing on TypeScript)
*.js
*.mjs
!.eslintrc.js
!eslint.config.js
# Scripts and config directories
scripts/
monitoring/
database/
docker-compose*.yml
Dockerfile*
# Documentation
*.md
docs/
# Test coverage
coverage/
**/coverage/
# IDE/Editor files
.vscode/
.idea/
*.swp
*.swo
# Angular specific
**/.angular/
**/src/polyfills.ts

1
.eslintrc.json Normal file
View file

@ -0,0 +1 @@
// This file is deprecated in ESLint v9. Use eslint.config.js instead.

22
.vscode/settings.json vendored
View file

@ -26,13 +26,31 @@
"editor.formatOnPaste": true, "editor.formatOnPaste": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": "explicit", "source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit" "source.organizeImports": "explicit"
}, },
"eslint.enable": true,
"eslint.validate": [
"typescript",
"javascript"
],
"eslint.run": "onType",
"eslint.workingDirectories": [
{
"mode": "auto"
}
],
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}, },
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}, },
"[json]": { "[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"

View file

@ -82,11 +82,11 @@ export class NotificationsComponent {
const diff = now.getTime() - timestamp.getTime(); const diff = now.getTime() - timestamp.getTime();
const minutes = Math.floor(diff / 60000); const minutes = Math.floor(diff / 60000);
if (minutes < 1) return 'Just now'; if (minutes < 1) {return 'Just now';}
if (minutes < 60) return `${minutes}m ago`; if (minutes < 60) {return `${minutes}m ago`;}
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`; if (hours < 24) {return `${hours}h ago`;}
const days = Math.floor(hours / 24); const days = Math.floor(hours / 24);
return `${days}d ago`; return `${days}d ago`;

View file

@ -161,8 +161,8 @@ export class PortfolioComponent implements OnInit, OnDestroy {
} }
getPnLColor(value: number): string { getPnLColor(value: number): string {
if (value > 0) return 'text-green-600'; if (value > 0) {return 'text-green-600';}
if (value < 0) return 'text-red-600'; if (value < 0) {return 'text-red-600';}
return 'text-gray-600'; return 'text-gray-600';
} }
} }

View file

@ -40,7 +40,7 @@ export class DrawdownChartComponent implements OnChanges {
} }
private renderChart(): void { private renderChart(): void {
if (!this.chartElement || !this.backtestResult) return; if (!this.chartElement || !this.backtestResult) {return;}
// Clean up previous chart if it exists // Clean up previous chart if it exists
if (this.chart) { if (this.chart) {

View file

@ -40,7 +40,7 @@ export class EquityChartComponent implements OnChanges {
} }
private renderChart(): void { private renderChart(): void {
if (!this.chartElement || !this.backtestResult) return; if (!this.chartElement || !this.backtestResult) {return;}
// Clean up previous chart if it exists // Clean up previous chart if it exists
if (this.chart) { if (this.chart) {

View file

@ -278,27 +278,27 @@ export class PerformanceMetricsComponent {
// Conditional classes // Conditional classes
getReturnClass(value: number): string { getReturnClass(value: number): string {
if (value > 0) return 'positive'; if (value > 0) {return 'positive';}
if (value < 0) return 'negative'; if (value < 0) {return 'negative';}
return ''; return '';
} }
getRatioClass(value: number): string { getRatioClass(value: number): string {
if (value >= 1.5) return 'positive'; if (value >= 1.5) {return 'positive';}
if (value >= 1) return 'neutral'; if (value >= 1) {return 'neutral';}
if (value < 0) return 'negative'; if (value < 0) {return 'negative';}
return ''; return '';
} }
getWinRateClass(value: number): string { getWinRateClass(value: number): string {
if (value >= 0.55) return 'positive'; if (value >= 0.55) {return 'positive';}
if (value >= 0.45) return 'neutral'; if (value >= 0.45) {return 'neutral';}
return 'negative'; return 'negative';
} }
getProfitFactorClass(value: number): string { getProfitFactorClass(value: number): string {
if (value >= 1.5) return 'positive'; if (value >= 1.5) {return 'positive';}
if (value >= 1) return 'neutral'; if (value >= 1) {return 'neutral';}
return 'negative'; return 'negative';
} }
} }

View file

@ -139,7 +139,7 @@ export class BacktestDialogComponent implements OnInit {
} }
addSymbol(symbol: string): void { addSymbol(symbol: string): void {
if (!symbol || this.selectedSymbols.includes(symbol)) return; if (!symbol || this.selectedSymbols.includes(symbol)) {return;}
this.selectedSymbols.push(symbol); this.selectedSymbols.push(symbol);
} }

View file

@ -126,7 +126,7 @@ export class StrategyDialogComponent implements OnInit {
} }
addSymbol(symbol: string): void { addSymbol(symbol: string): void {
if (!symbol || this.selectedSymbols.includes(symbol)) return; if (!symbol || this.selectedSymbols.includes(symbol)) {return;}
this.selectedSymbols.push(symbol); this.selectedSymbols.push(symbol);
} }

View file

@ -67,7 +67,9 @@ export class StrategyDetailsComponent implements OnChanges {
} }
loadStrategyData(): void { loadStrategyData(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
// In a real implementation, these would call API methods to fetch the data // In a real implementation, these would call API methods to fetch the data
this.loadSignals(); this.loadSignals();
@ -75,7 +77,9 @@ export class StrategyDetailsComponent implements OnChanges {
this.loadPerformance(); this.loadPerformance();
} }
loadSignals(): void { loadSignals(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
this.isLoadingSignals = true; this.isLoadingSignals = true;
@ -100,7 +104,9 @@ export class StrategyDetailsComponent implements OnChanges {
} }
loadTrades(): void { loadTrades(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
this.isLoadingTrades = true; this.isLoadingTrades = true;
@ -140,7 +146,9 @@ export class StrategyDetailsComponent implements OnChanges {
}; };
} }
listenForUpdates(): void { listenForUpdates(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
// Subscribe to strategy signals // Subscribe to strategy signals
this.webSocketService.getStrategySignals(this.strategy.id).subscribe((signal: any) => { this.webSocketService.getStrategySignals(this.strategy.id).subscribe((signal: any) => {
@ -186,7 +194,9 @@ export class StrategyDetailsComponent implements OnChanges {
* Update performance metrics when new trades come in * Update performance metrics when new trades come in
*/ */
private updatePerformanceMetrics(): void { private updatePerformanceMetrics(): void {
if (!this.strategy || this.trades.length === 0) return; if (!this.strategy || this.trades.length === 0) {
return;
}
// Calculate basic metrics // Calculate basic metrics
const winningTrades = this.trades.filter(t => t.pnl > 0); const winningTrades = this.trades.filter(t => t.pnl > 0);
@ -201,6 +211,8 @@ export class StrategyDetailsComponent implements OnChanges {
...currentPerformance, ...currentPerformance,
totalTrades: this.trades.length, totalTrades: this.trades.length,
winRate: winRate, winRate: winRate,
winningTrades,
losingTrades,
totalReturn: (currentPerformance.totalReturn || 0) + totalPnl / 10000, // Approximate totalReturn: (currentPerformance.totalReturn || 0) + totalPnl / 10000, // Approximate
}; };
@ -242,7 +254,9 @@ export class StrategyDetailsComponent implements OnChanges {
* Open the backtest dialog to run a backtest for this strategy * Open the backtest dialog to run a backtest for this strategy
*/ */
openBacktestDialog(): void { openBacktestDialog(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
const dialogRef = this.dialog.open(BacktestDialogComponent, { const dialogRef = this.dialog.open(BacktestDialogComponent, {
width: '800px', width: '800px',
@ -261,7 +275,9 @@ export class StrategyDetailsComponent implements OnChanges {
* Open the strategy edit dialog * Open the strategy edit dialog
*/ */
openEditDialog(): void { openEditDialog(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
const dialogRef = this.dialog.open(StrategyDialogComponent, { const dialogRef = this.dialog.open(StrategyDialogComponent, {
width: '600px', width: '600px',
@ -280,7 +296,9 @@ export class StrategyDetailsComponent implements OnChanges {
* Start the strategy * Start the strategy
*/ */
activateStrategy(): void { activateStrategy(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
this.strategyService.startStrategy(this.strategy.id).subscribe({ this.strategyService.startStrategy(this.strategy.id).subscribe({
next: response => { next: response => {
@ -298,7 +316,9 @@ export class StrategyDetailsComponent implements OnChanges {
* Pause the strategy * Pause the strategy
*/ */
pauseStrategy(): void { pauseStrategy(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
this.strategyService.pauseStrategy(this.strategy.id).subscribe({ this.strategyService.pauseStrategy(this.strategy.id).subscribe({
next: response => { next: response => {
@ -316,7 +336,9 @@ export class StrategyDetailsComponent implements OnChanges {
* Stop the strategy * Stop the strategy
*/ */
stopStrategy(): void { stopStrategy(): void {
if (!this.strategy) return; if (!this.strategy) {
return;
}
this.strategyService.stopStrategy(this.strategy.id).subscribe({ this.strategyService.stopStrategy(this.strategy.id).subscribe({
next: response => { next: response => {
@ -332,7 +354,9 @@ export class StrategyDetailsComponent implements OnChanges {
// Methods to generate mock data // Methods to generate mock data
private generateMockSignals(): any[] { private generateMockSignals(): any[] {
if (!this.strategy) return []; if (!this.strategy) {
return [];
}
const signals = []; const signals = [];
const actions = ['BUY', 'SELL', 'HOLD']; const actions = ['BUY', 'SELL', 'HOLD'];
@ -358,7 +382,9 @@ export class StrategyDetailsComponent implements OnChanges {
} }
private generateMockTrades(): any[] { private generateMockTrades(): any[] {
if (!this.strategy) return []; if (!this.strategy) {
return [];
}
const trades = []; const trades = [];
const now = new Date(); const now = new Date();

View file

@ -1,5 +1,5 @@
import { Injectable, signal } from '@angular/core'; import { Injectable, signal } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { filter, map } from 'rxjs/operators'; import { filter, map } from 'rxjs/operators';
export interface WebSocketMessage { export interface WebSocketMessage {
@ -204,7 +204,7 @@ export class WebSocketService {
// Cleanup // Cleanup
disconnect() { disconnect() {
this.connections.forEach((ws, serviceName) => { this.connections.forEach((ws, _serviceName) => {
if (ws.readyState === WebSocket.OPEN) { if (ws.readyState === WebSocket.OPEN) {
ws.close(); ws.close();
} }

View file

@ -6,10 +6,6 @@
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "types": []
}, },
"include": [ "include": ["src/**/*.ts"],
"src/**/*.ts" "exclude": ["src/**/*.spec.ts"]
],
"exclude": [
"src/**/*.spec.ts"
]
} }

View file

@ -4,11 +4,7 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": ["jasmine"]
"jasmine"
]
}, },
"include": [ "include": ["src/**/*.ts"]
"src/**/*.ts"
]
} }

View file

@ -16,7 +16,7 @@ const getEvery24HourCron = (): string => {
export const proxyProvider: ProviderConfig = { export const proxyProvider: ProviderConfig = {
name: 'proxy-provider', name: 'proxy-provider',
operations: { operations: {
'fetch-and-check': async (payload: { sources?: string[] }) => { 'fetch-and-check': async (_payload: { sources?: string[] }) => {
const { proxyService } = await import('./proxy.tasks'); const { proxyService } = await import('./proxy.tasks');
const { queueManager } = await import('../services/queue.service'); const { queueManager } = await import('../services/queue.service');
const { processItems } = await import('../utils/batch-helpers'); const { processItems } = await import('../utils/batch-helpers');

View file

@ -172,8 +172,8 @@ let proxyStats: ProxySource[] = PROXY_CONFIG.PROXY_SOURCES.map(source => ({
async function updateProxyStats(sourceId: string, success: boolean) { async function updateProxyStats(sourceId: string, success: boolean) {
const source = proxyStats.find(s => s.id === sourceId); const source = proxyStats.find(s => s.id === sourceId);
if (source !== undefined) { if (source !== undefined) {
if (typeof source.working !== 'number') source.working = 0; if (typeof source.working !== 'number') {source.working = 0;}
if (typeof source.total !== 'number') source.total = 0; if (typeof source.total !== 'number') {source.total = 0;}
source.total += 1; source.total += 1;
if (success) { if (success) {
source.working += 1; source.working += 1;
@ -400,7 +400,7 @@ export async function fetchProxiesFromSource(source: ProxySource): Promise<Proxy
for (const line of lines) { for (const line of lines) {
let trimmed = line.trim(); let trimmed = line.trim();
trimmed = cleanProxyUrl(trimmed); trimmed = cleanProxyUrl(trimmed);
if (!trimmed || trimmed.startsWith('#')) continue; if (!trimmed || trimmed.startsWith('#')) {continue;}
// Parse formats like "host:port" or "host:port:user:pass" // Parse formats like "host:port" or "host:port:user:pass"
const parts = trimmed.split(':'); const parts = trimmed.split(':');

View file

@ -82,7 +82,7 @@ export function createProviderRegistry(): ProviderRegistry {
function getAllScheduledJobs(): Array<{ provider: string; job: ScheduledJob }> { function getAllScheduledJobs(): Array<{ provider: string; job: ScheduledJob }> {
const allJobs: Array<{ provider: string; job: ScheduledJob }> = []; const allJobs: Array<{ provider: string; job: ScheduledJob }> = [];
for (const [key, config] of providers) { for (const [, config] of providers) {
if (config.scheduledJobs) { if (config.scheduledJobs) {
for (const job of config.scheduledJobs) { for (const job of config.scheduledJobs) {
allJobs.push({ allJobs.push({

View file

@ -5,7 +5,15 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**", "**/tests/**", "**/__tests__/**"], "exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**",
"**/tests/**",
"**/__tests__/**"
],
"references": [ "references": [
{ "path": "../../libs/types" }, { "path": "../../libs/types" },
{ "path": "../../libs/config" }, { "path": "../../libs/config" },

View file

@ -5,7 +5,15 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**", "**/tests/**", "**/__tests__/**"], "exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**",
"**/tests/**",
"**/__tests__/**"
],
"references": [ "references": [
{ "path": "../../libs/types" }, { "path": "../../libs/types" },
{ "path": "../../libs/config" }, { "path": "../../libs/config" },

View file

@ -1,4 +1,4 @@
import { PortfolioSnapshot, Trade } from '../portfolio/portfolio-manager.ts'; import { PortfolioSnapshot } from '../portfolio/portfolio-manager';
export interface PerformanceMetrics { export interface PerformanceMetrics {
totalReturn: number; totalReturn: number;
@ -67,8 +67,8 @@ export class PerformanceAnalyzer {
}; };
} }
private calculateReturns(period: 'daily' | 'weekly' | 'monthly'): number[] { private calculateReturns(_period: 'daily' | 'weekly' | 'monthly'): number[] {
if (this.snapshots.length < 2) return []; if (this.snapshots.length < 2) {return [];}
const returns: number[] = []; const returns: number[] = [];
@ -83,7 +83,7 @@ export class PerformanceAnalyzer {
} }
private calculateTotalReturn(): number { private calculateTotalReturn(): number {
if (this.snapshots.length < 2) return 0; if (this.snapshots.length < 2) {return 0;}
const firstValue = this.snapshots[0].totalValue; const firstValue = this.snapshots[0].totalValue;
const lastValue = this.snapshots[this.snapshots.length - 1].totalValue; const lastValue = this.snapshots[this.snapshots.length - 1].totalValue;
@ -92,14 +92,14 @@ export class PerformanceAnalyzer {
} }
private calculateAnnualizedReturn(returns: number[]): number { private calculateAnnualizedReturn(returns: number[]): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
return Math.pow(1 + avgReturn, 252) - 1; // 252 trading days per year return Math.pow(1 + avgReturn, 252) - 1; // 252 trading days per year
} }
private calculateVolatility(returns: number[]): number { private calculateVolatility(returns: number[]): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
@ -109,19 +109,19 @@ export class PerformanceAnalyzer {
} }
private calculateSharpeRatio(returns: number[], riskFreeRate: number): number { private calculateSharpeRatio(returns: number[], riskFreeRate: number): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const annualizedReturn = Math.pow(1 + avgReturn, 252) - 1; const annualizedReturn = Math.pow(1 + avgReturn, 252) - 1;
const volatility = this.calculateVolatility(returns); const volatility = this.calculateVolatility(returns);
if (volatility === 0) return 0; if (volatility === 0) {return 0;}
return (annualizedReturn - riskFreeRate) / volatility; return (annualizedReturn - riskFreeRate) / volatility;
} }
private calculateMaxDrawdown(): number { private calculateMaxDrawdown(): number {
if (this.snapshots.length === 0) return 0; if (this.snapshots.length === 0) {return 0;}
let maxDrawdown = 0; let maxDrawdown = 0;
let peak = this.snapshots[0].totalValue; let peak = this.snapshots[0].totalValue;
@ -139,7 +139,7 @@ export class PerformanceAnalyzer {
} }
private calculateBeta(returns: number[]): number { private calculateBeta(returns: number[]): number {
if (returns.length === 0 || this.benchmarkReturns.length === 0) return 1.0; if (returns.length === 0 || this.benchmarkReturns.length === 0) {return 1.0;}
// Simple beta calculation - would need actual benchmark data // Simple beta calculation - would need actual benchmark data
return 1.0; // Placeholder return 1.0; // Placeholder
@ -157,7 +157,7 @@ export class PerformanceAnalyzer {
const annualizedReturn = this.calculateAnnualizedReturn(returns); const annualizedReturn = this.calculateAnnualizedReturn(returns);
const maxDrawdown = this.calculateMaxDrawdown(); const maxDrawdown = this.calculateMaxDrawdown();
if (maxDrawdown === 0) return 0; if (maxDrawdown === 0) {return 0;}
return annualizedReturn / maxDrawdown; return annualizedReturn / maxDrawdown;
} }
@ -166,16 +166,16 @@ export class PerformanceAnalyzer {
const annualizedReturn = this.calculateAnnualizedReturn(returns); const annualizedReturn = this.calculateAnnualizedReturn(returns);
const downsideDeviation = this.calculateDownsideDeviation(returns); const downsideDeviation = this.calculateDownsideDeviation(returns);
if (downsideDeviation === 0) return 0; if (downsideDeviation === 0) {return 0;}
return (annualizedReturn - riskFreeRate) / downsideDeviation; return (annualizedReturn - riskFreeRate) / downsideDeviation;
} }
private calculateDownsideDeviation(returns: number[]): number { private calculateDownsideDeviation(returns: number[]): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const negativeReturns = returns.filter(ret => ret < 0); const negativeReturns = returns.filter(ret => ret < 0);
if (negativeReturns.length === 0) return 0; if (negativeReturns.length === 0) {return 0;}
const avgNegativeReturn = const avgNegativeReturn =
negativeReturns.reduce((sum, ret) => sum + ret, 0) / negativeReturns.length; negativeReturns.reduce((sum, ret) => sum + ret, 0) / negativeReturns.length;
@ -187,7 +187,7 @@ export class PerformanceAnalyzer {
} }
private calculateVaR(returns: number[], confidence: number): number { private calculateVaR(returns: number[], confidence: number): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const sortedReturns = returns.slice().sort((a, b) => a - b); const sortedReturns = returns.slice().sort((a, b) => a - b);
const index = Math.floor((1 - confidence) * sortedReturns.length); const index = Math.floor((1 - confidence) * sortedReturns.length);
@ -196,13 +196,13 @@ export class PerformanceAnalyzer {
} }
private calculateCVaR(returns: number[], confidence: number): number { private calculateCVaR(returns: number[], confidence: number): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const sortedReturns = returns.slice().sort((a, b) => a - b); const sortedReturns = returns.slice().sort((a, b) => a - b);
const cutoffIndex = Math.floor((1 - confidence) * sortedReturns.length); const cutoffIndex = Math.floor((1 - confidence) * sortedReturns.length);
const tailReturns = sortedReturns.slice(0, cutoffIndex + 1); const tailReturns = sortedReturns.slice(0, cutoffIndex + 1);
if (tailReturns.length === 0) return 0; if (tailReturns.length === 0) {return 0;}
const avgTailReturn = tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length; const avgTailReturn = tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length;
return -avgTailReturn; // Return as positive value return -avgTailReturn; // Return as positive value

View file

@ -2,8 +2,6 @@ import { serve } from '@hono/node-server';
import { Hono } from 'hono'; import { Hono } from 'hono';
import { config } from '@stock-bot/config'; import { config } from '@stock-bot/config';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
import { PerformanceAnalyzer } from './analytics/performance-analyzer.ts';
import { PortfolioManager } from './portfolio/portfolio-manager.ts';
const app = new Hono(); const app = new Hono();
const logger = getLogger('portfolio-service'); const logger = getLogger('portfolio-service');

View file

@ -5,7 +5,15 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**", "**/tests/**", "**/__tests__/**"], "exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**",
"**/tests/**",
"**/__tests__/**"
],
"references": [ "references": [
{ "path": "../../libs/types" }, { "path": "../../libs/types" },
{ "path": "../../libs/config" }, { "path": "../../libs/config" },

View file

@ -33,27 +33,31 @@ export class IndicatorsService {
for (const indicator of request.indicators) { for (const indicator of request.indicators) {
try { try {
switch (indicator.toLowerCase()) { switch (indicator.toLowerCase()) {
case 'sma': case 'sma': {
const smaPeriod = request.parameters?.smaPeriod || 20; const smaPeriod = request.parameters?.smaPeriod || 20;
results.sma = sma(request.data, smaPeriod); results.sma = sma(request.data, smaPeriod);
break; break;
}
case 'ema': case 'ema': {
const emaPeriod = request.parameters?.emaPeriod || 20; const emaPeriod = request.parameters?.emaPeriod || 20;
results.ema = ema(request.data, emaPeriod); results.ema = ema(request.data, emaPeriod);
break; break;
}
case 'rsi': case 'rsi': {
const rsiPeriod = request.parameters?.rsiPeriod || 14; const rsiPeriod = request.parameters?.rsiPeriod || 14;
results.rsi = rsi(request.data, rsiPeriod); results.rsi = rsi(request.data, rsiPeriod);
break; break;
}
case 'macd': case 'macd': {
const fast = request.parameters?.macdFast || 12; const fast = request.parameters?.macdFast || 12;
const slow = request.parameters?.macdSlow || 26; const slow = request.parameters?.macdSlow || 26;
const signal = request.parameters?.macdSignal || 9; const signal = request.parameters?.macdSignal || 9;
results.macd = macd(request.data, fast, slow, signal).macd; results.macd = macd(request.data, fast, slow, signal).macd;
break; break;
}
case 'stochastic': case 'stochastic':
// TODO: Implement stochastic oscillator // TODO: Implement stochastic oscillator

View file

@ -5,7 +5,15 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**", "**/tests/**", "**/__tests__/**"], "exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**",
"**/tests/**",
"**/__tests__/**"
],
"references": [ "references": [
{ "path": "../../libs/types" }, { "path": "../../libs/types" },
{ "path": "../../libs/config" }, { "path": "../../libs/config" },

View file

@ -8,7 +8,8 @@
"devvvvv": "bun --watch src/index.ts", "devvvvv": "bun --watch src/index.ts",
"build": "bun build src/index.ts --outdir dist --target node", "build": "bun build src/index.ts --outdir dist --target node",
"start": "bun dist/index.js", "start": "bun dist/index.js",
"test": "bun test", "clean": "rm -rf dist", "test": "bun test",
"clean": "rm -rf dist",
"backtest": "bun src/cli/index.ts", "backtest": "bun src/cli/index.ts",
"optimize": "bun src/cli/index.ts optimize", "optimize": "bun src/cli/index.ts optimize",
"cli": "bun src/cli/index.ts" "cli": "bun src/cli/index.ts"

View file

@ -1,4 +1,3 @@
import { create } from 'domain';
import { DataFrame } from '@stock-bot/data-frame'; import { DataFrame } from '@stock-bot/data-frame';
import { EventBus } from '@stock-bot/event-bus'; import { EventBus } from '@stock-bot/event-bus';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
@ -198,7 +197,7 @@ export class HybridMode extends ExecutionMode {
private overrideIndicatorCalculations(eventMode: EventMode): void { private overrideIndicatorCalculations(eventMode: EventMode): void {
// Override the event mode's indicator calculations to use pre-computed values // Override the event mode's indicator calculations to use pre-computed values
// This is a simplified approach - in production you'd want a more sophisticated interface // This is a simplified approach - in production you'd want a more sophisticated interface
const originalCalculateIndicators = (eventMode as any).calculateIndicators; const _originalCalculateIndicators = (eventMode as any).calculateIndicators;
(eventMode as any).calculateIndicators = (symbol: string, index: number) => { (eventMode as any).calculateIndicators = (symbol: string, index: number) => {
const indicators: Record<string, number> = {}; const indicators: Record<string, number> = {};

View file

@ -19,7 +19,7 @@ export class LiveMode extends ExecutionMode {
return new Date(); // Real time return new Date(); // Real time
} }
async getMarketData(symbol: string): Promise<MarketData> { async getMarketData(_symbol: string): Promise<MarketData> {
// TODO: Get live market data // TODO: Get live market data
throw new Error('Live market data fetching not implemented yet'); throw new Error('Live market data fetching not implemented yet');
} }

View file

@ -82,7 +82,7 @@ async function runBacktest(options: CLIBacktestConfig): Promise<void> {
// Subscribe to progress updates // Subscribe to progress updates
eventBus.subscribe('backtest.update', message => { eventBus.subscribe('backtest.update', message => {
const { backtestId, progress, ...data } = message.data; const { backtestId: _backtestId, progress, ...data } = message.data;
console.log(`Progress: ${progress}%`, data); console.log(`Progress: ${progress}%`, data);
}); });
@ -172,7 +172,7 @@ async function saveResults(result: any, outputPath: string): Promise<void> {
} }
function convertTradesToCSV(trades: any[]): string { function convertTradesToCSV(trades: any[]): string {
if (trades.length === 0) return 'No trades executed\n'; if (trades.length === 0) {return 'No trades executed\n';}
const headers = Object.keys(trades[0]).join(','); const headers = Object.keys(trades[0]).join(',');
const rows = trades.map(trade => const rows = trades.map(trade =>
@ -259,7 +259,7 @@ program
const strategies = options.strategies.split(',').map((s: string) => s.trim()); const strategies = options.strategies.split(',').map((s: string) => s.trim());
console.log(`Comparing strategies: ${strategies.join(', ')}`); console.log(`Comparing strategies: ${strategies.join(', ')}`);
const results: any[] = []; const _results: any[] = [];
for (const strategy of strategies) { for (const strategy of strategies) {
console.log(`\nRunning ${strategy}...`); console.log(`\nRunning ${strategy}...`);

View file

@ -4,7 +4,7 @@
*/ */
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
const logger = getLogger('execution-mode'); const _logger = getLogger('execution-mode');
export interface Order { export interface Order {
id: string; id: string;
@ -55,7 +55,7 @@ export enum BacktestMode {
} }
export class ModeFactory { export class ModeFactory {
static create(mode: BacktestMode, config?: any): ExecutionMode { static create(mode: BacktestMode, _config?: any): ExecutionMode {
switch (mode) { switch (mode) {
case BacktestMode.LIVE: case BacktestMode.LIVE:
// TODO: Import and create LiveMode // TODO: Import and create LiveMode

View file

@ -5,7 +5,15 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**", "**/tests/**", "**/__tests__/**"], "exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**",
"**/tests/**",
"**/__tests__/**"
],
"references": [ "references": [
{ "path": "../../libs/types" }, { "path": "../../libs/types" },
{ "path": "../../libs/config" }, { "path": "../../libs/config" },

633
bun.lock

File diff suppressed because it is too large Load diff

77
eslint.config.js Normal file
View file

@ -0,0 +1,77 @@
import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
export default [
// Global ignores first
{
ignores: [
'dist/**',
'build/**',
'node_modules/**',
'**/*.js',
'**/*.mjs',
'**/*.d.ts',
'.turbo/**',
'coverage/**',
'scripts/**',
'monitoring/**',
'database/**',
'**/.angular/**',
'**/src/polyfills.ts',
],
},
// Base JavaScript configuration
js.configs.recommended,
// TypeScript configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// Disable base rules that are covered by TypeScript equivalents
'no-unused-vars': 'off',
'no-undef': 'off',
// TypeScript specific rules
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
// General rules
'no-console': 'warn',
'no-debugger': 'error',
'no-var': 'error',
'prefer-const': 'error',
eqeqeq: ['error', 'always'],
curly: ['error', 'all'],
},
},
// Test files configuration
{
files: ['**/*.test.ts', '**/*.spec.ts', '**/test/**/*', '**/tests/**/*'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'no-console': 'off',
},
},
];

View file

@ -173,7 +173,7 @@ export class RedisConnectionManager {
try { try {
await connection.ping(); await connection.ping();
details[`shared:${name}`] = true; details[`shared:${name}`] = true;
} catch (error) { } catch (_error) {
details[`shared:${name}`] = false; details[`shared:${name}`] = false;
allHealthy = false; allHealthy = false;
} }
@ -184,7 +184,7 @@ export class RedisConnectionManager {
try { try {
await connection.ping(); await connection.ping();
details[`unique:${name}`] = true; details[`unique:${name}`] = true;
} catch (error) { } catch (_error) {
details[`unique:${name}`] = false; details[`unique:${name}`] = false;
allHealthy = false; allHealthy = false;
} }

View file

@ -87,7 +87,7 @@ export class RedisCache implements CacheProvider {
} }
private updateStats(hit: boolean, error = false): void { private updateStats(hit: boolean, error = false): void {
if (!this.enableMetrics) return; if (!this.enableMetrics) {return;}
if (error) { if (error) {
this.stats.errors++; this.stats.errors++;

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -5,8 +5,13 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**/*", "**/tests/**/*"], "exclude": [
"references": [ "node_modules",
{ "path": "../types" } "dist",
] "**/*.test.ts",
"**/*.spec.ts",
"**/test/**/*",
"**/tests/**/*"
],
"references": [{ "path": "../types" }]
} }

View file

@ -35,7 +35,7 @@ export class DataFrame {
} }
private inferColumns(): string[] { private inferColumns(): string[] {
if (this.data.length === 0) return []; if (this.data.length === 0) {return [];}
const columns = new Set<string>(); const columns = new Set<string>();
for (const row of this.data) { for (const row of this.data) {
@ -46,7 +46,7 @@ export class DataFrame {
} }
private validateAndCleanData(): void { private validateAndCleanData(): void {
if (this.data.length === 0) return; if (this.data.length === 0) {return;}
// Ensure all rows have the same columns // Ensure all rows have the same columns
for (let i = 0; i < this.data.length; i++) { for (let i = 0; i < this.data.length; i++) {
@ -224,7 +224,7 @@ export class DataFrame {
const aVal = a[column]; const aVal = a[column];
const bVal = b[column]; const bVal = b[column];
if (aVal === bVal) return 0; if (aVal === bVal) {return 0;}
const comparison = aVal > bVal ? 1 : -1; const comparison = aVal > bVal ? 1 : -1;
return ascending ? comparison : -comparison; return ascending ? comparison : -comparison;

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../logger" }, { "path": "../utils" }]
{ "path": "../types" },
{ "path": "../logger" },
{ "path": "../utils" }
]
} }

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -1,6 +1,5 @@
import type { Logger } from '@stock-bot/logger'; import type { Logger } from '@stock-bot/logger';
import { AdapterFactory } from './adapters/index'; import { AdapterFactory } from './adapters/index';
import { ProxyManager } from './proxy-manager';
import type { HttpClientConfig, HttpResponse, RequestConfig } from './types'; import type { HttpClientConfig, HttpResponse, RequestConfig } from './types';
import { HttpError } from './types'; import { HttpError } from './types';
@ -144,7 +143,7 @@ export class HttpClient {
const elapsed = Date.now() - startTime; const elapsed = Date.now() - startTime;
this.logger?.debug('Adapter failed successful', { this.logger?.debug('Adapter failed successful', {
url: config.url, url: config.url,
elapsedMs: Date.now() - startTime, elapsedMs: elapsed,
}); });
clearTimeout(timeoutId); clearTimeout(timeoutId);

View file

@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig, type AxiosInstance } from 'axios'; import { AxiosRequestConfig } from 'axios';
import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpProxyAgent } from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent';
import { SocksProxyAgent } from 'socks-proxy-agent'; import { SocksProxyAgent } from 'socks-proxy-agent';

View file

@ -5,9 +5,13 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/test/**/*", "**/tests/**/*"], "exclude": [
"references": [ "node_modules",
{ "path": "../types" }, "dist",
{ "path": "../logger" } "**/*.test.ts",
] "**/*.spec.ts",
"**/test/**/*",
"**/tests/**/*"
],
"references": [{ "path": "../types" }, { "path": "../logger" }]
} }

View file

@ -5,8 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }]
{ "path": "../types" },
{ "path": "../config" }
]
} }

View file

@ -46,6 +46,9 @@
"README.md" "README.md"
], ],
"paths": { "paths": {
"*": ["node_modules/*", "../../node_modules/*"] "*": [
"node_modules/*",
"../../node_modules/*"
]
} }
} }

View file

@ -141,7 +141,7 @@ export class MongoDBAggregationBuilder {
this.from('sentiment_data'); this.from('sentiment_data');
const matchConditions: any = {}; const matchConditions: any = {};
if (symbol) matchConditions.symbol = symbol; if (symbol) {matchConditions.symbol = symbol;}
if (timeframe) { if (timeframe) {
matchConditions.timestamp = { matchConditions.timestamp = {
$gte: timeframe.start, $gte: timeframe.start,

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -12,7 +12,8 @@
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"clean": "rimraf dist" "clean": "rimraf dist"
}, },
"dependencies": { "@stock-bot/config": "*", "dependencies": {
"@stock-bot/config": "*",
"@stock-bot/logger": "*", "@stock-bot/logger": "*",
"@stock-bot/types": "*", "@stock-bot/types": "*",
"pg": "^8.11.3", "pg": "^8.11.3",

View file

@ -327,7 +327,7 @@ export class PostgreSQLClient {
} }
private setupErrorHandlers(): void { private setupErrorHandlers(): void {
if (!this.pool) return; if (!this.pool) {return;}
this.pool.on('error', error => { this.pool.on('error', error => {
this.logger.error('PostgreSQL pool error:', error); this.logger.error('PostgreSQL pool error:', error);

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -4,7 +4,8 @@
"description": "Graceful shutdown management for Stock Bot platform", "description": "Graceful shutdown management for Stock Bot platform",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "scripts": { "types": "dist/index.d.ts",
"scripts": {
"build": "tsc", "build": "tsc",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"test": "bun test" "test": "bun test"

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -4,7 +4,8 @@
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "references": [ "include": ["src/**/*"],
"references": [
{ "path": "../types" }, { "path": "../types" },
{ "path": "../logger" }, { "path": "../logger" },
{ "path": "../utils" }, { "path": "../utils" },

View file

@ -7,7 +7,7 @@
* Calculate percentage change between two values * Calculate percentage change between two values
*/ */
export function percentageChange(oldValue: number, newValue: number): number { export function percentageChange(oldValue: number, newValue: number): number {
if (oldValue === 0) return 0; if (oldValue === 0) {return 0;}
return ((newValue - oldValue) / oldValue) * 100; return ((newValue - oldValue) / oldValue) * 100;
} }
@ -15,7 +15,7 @@ export function percentageChange(oldValue: number, newValue: number): number {
* Calculate simple return * Calculate simple return
*/ */
export function simpleReturn(initialPrice: number, finalPrice: number): number { export function simpleReturn(initialPrice: number, finalPrice: number): number {
if (initialPrice === 0) return 0; if (initialPrice === 0) {return 0;}
return (finalPrice - initialPrice) / initialPrice; return (finalPrice - initialPrice) / initialPrice;
} }
@ -23,7 +23,7 @@ export function simpleReturn(initialPrice: number, finalPrice: number): number {
* Calculate logarithmic return * Calculate logarithmic return
*/ */
export function logReturn(initialPrice: number, finalPrice: number): number { export function logReturn(initialPrice: number, finalPrice: number): number {
if (initialPrice <= 0 || finalPrice <= 0) return 0; if (initialPrice <= 0 || finalPrice <= 0) {return 0;}
return Math.log(finalPrice / initialPrice); return Math.log(finalPrice / initialPrice);
} }
@ -31,7 +31,7 @@ export function logReturn(initialPrice: number, finalPrice: number): number {
* Calculate compound annual growth rate (CAGR) * Calculate compound annual growth rate (CAGR)
*/ */
export function cagr(startValue: number, endValue: number, years: number): number { export function cagr(startValue: number, endValue: number, years: number): number {
if (years <= 0 || startValue <= 0 || endValue <= 0) return 0; if (years <= 0 || startValue <= 0 || endValue <= 0) {return 0;}
return Math.pow(endValue / startValue, 1 / years) - 1; return Math.pow(endValue / startValue, 1 / years) - 1;
} }
@ -91,8 +91,8 @@ export function internalRateOfReturn(
dnpv += (-j * cashFlows[j]) / Math.pow(1 + rate, j + 1); dnpv += (-j * cashFlows[j]) / Math.pow(1 + rate, j + 1);
} }
if (Math.abs(npv) < 1e-10) break; if (Math.abs(npv) < 1e-10) {break;}
if (Math.abs(dnpv) < 1e-10) break; if (Math.abs(dnpv) < 1e-10) {break;}
rate = rate - npv / dnpv; rate = rate - npv / dnpv;
} }
@ -186,7 +186,7 @@ export function bondYield(
); );
const diff = calculatedPrice - price; const diff = calculatedPrice - price;
if (Math.abs(diff) < tolerance) break; if (Math.abs(diff) < tolerance) {break;}
// Numerical derivative // Numerical derivative
const delta = 0.0001; const delta = 0.0001;
@ -199,7 +199,7 @@ export function bondYield(
); );
const derivative = (priceUp - calculatedPrice) / delta; const derivative = (priceUp - calculatedPrice) / delta;
if (Math.abs(derivative) < tolerance) break; if (Math.abs(derivative) < tolerance) {break;}
yield_ = yield_ - diff / derivative; yield_ = yield_ - diff / derivative;
} }
@ -358,7 +358,7 @@ export function dividendDiscountModel(
growthRate: number, growthRate: number,
discountRate: number discountRate: number
): number { ): number {
if (discountRate <= growthRate) return NaN; // Indeterminate if (discountRate <= growthRate) {return NaN;} // Indeterminate
return (currentDividend * (1 + growthRate)) / (discountRate - growthRate); return (currentDividend * (1 + growthRate)) / (discountRate - growthRate);
} }

View file

@ -488,7 +488,7 @@ export function dccModel(
const T = data[0].length; const T = data[0].length;
// Initialize parameters [alpha, beta] // Initialize parameters [alpha, beta]
let params = [0.01, 0.95]; const params = [0.01, 0.95];
// Standardize data (assume unit variance for simplicity) // Standardize data (assume unit variance for simplicity)
const standardizedData = data.map(series => { const standardizedData = data.map(series => {
@ -918,7 +918,7 @@ function shuffleArray<T>(array: T[]): T[] {
* Helper function to calculate the average of an array of numbers * Helper function to calculate the average of an array of numbers
*/ */
function average(arr: number[]): number { function average(arr: number[]): number {
if (arr.length === 0) return 0; if (arr.length === 0) {return 0;}
return arr.reduce((a, b) => a + b, 0) / arr.length; return arr.reduce((a, b) => a + b, 0) / arr.length;
} }
@ -963,8 +963,8 @@ function erf(x: number): number {
function betaIncomplete(a: number, b: number, x: number): number { function betaIncomplete(a: number, b: number, x: number): number {
// Better approximation of incomplete beta function // Better approximation of incomplete beta function
if (x === 0) return 0; if (x === 0) {return 0;}
if (x === 1) return 1; if (x === 1) {return 1;}
// Use continued fraction approximation (Lentz's algorithm) // Use continued fraction approximation (Lentz's algorithm)
const fpmin = 1e-30; const fpmin = 1e-30;
@ -984,7 +984,7 @@ function betaIncomplete(a: number, b: number, x: number): number {
function betaContinuedFraction(a: number, b: number, x: number): number { function betaContinuedFraction(a: number, b: number, x: number): number {
let c = 1; let c = 1;
let d = 1 - ((a + b) * x) / (a + 1); let d = 1 - ((a + b) * x) / (a + 1);
if (Math.abs(d) < fpmin) d = fpmin; if (Math.abs(d) < fpmin) {d = fpmin;}
d = 1 / d; d = 1 / d;
let h = d; let h = d;
@ -992,22 +992,22 @@ function betaIncomplete(a: number, b: number, x: number): number {
const m2 = 2 * m; const m2 = 2 * m;
const aa = (m * (b - m) * x) / ((a + m2 - 1) * (a + m2)); const aa = (m * (b - m) * x) / ((a + m2 - 1) * (a + m2));
d = 1 + aa * d; d = 1 + aa * d;
if (Math.abs(d) < fpmin) d = fpmin; if (Math.abs(d) < fpmin) {d = fpmin;}
c = 1 + aa / c; c = 1 + aa / c;
if (Math.abs(c) < fpmin) c = fpmin; if (Math.abs(c) < fpmin) {c = fpmin;}
d = 1 / d; d = 1 / d;
h *= d * c; h *= d * c;
const bb = (-(a + m) * (a + b + m) * x) / ((a + m2) * (a + m2 + 1)); const bb = (-(a + m) * (a + b + m) * x) / ((a + m2) * (a + m2 + 1));
d = 1 + bb * d; d = 1 + bb * d;
if (Math.abs(d) < fpmin) d = fpmin; if (Math.abs(d) < fpmin) {d = fpmin;}
c = 1 + bb / c; c = 1 + bb / c;
if (Math.abs(c) < fpmin) c = fpmin; if (Math.abs(c) < fpmin) {c = fpmin;}
d = 1 / d; d = 1 / d;
const del = d * c; const del = d * c;
h *= del; h *= del;
if (Math.abs(del - 1) < eps) break; if (Math.abs(del - 1) < eps) {break;}
} }
return h; return h;
@ -1055,11 +1055,11 @@ function eigenDecomposition(matrix: number[][]): {
const newLambda = Av.reduce((sum, val, i) => sum + val * v[i], 0); const newLambda = Av.reduce((sum, val, i) => sum + val * v[i], 0);
const norm = Math.sqrt(Av.reduce((sum, val) => sum + val * val, 0)); const norm = Math.sqrt(Av.reduce((sum, val) => sum + val * val, 0));
if (norm === 0) break; if (norm === 0) {break;}
v = Av.map(val => val / norm); v = Av.map(val => val / norm);
if (Math.abs(newLambda - lambda) < 1e-10) break; if (Math.abs(newLambda - lambda) < 1e-10) {break;}
lambda = newLambda; lambda = newLambda;
} }
@ -1215,8 +1215,8 @@ function arModel(y: number[], lag: number): { rss: number } {
function fCDF(f: number, df1: number, df2: number): number { function fCDF(f: number, df1: number, df2: number): number {
// Approximation for F distribution CDF // Approximation for F distribution CDF
if (f <= 0) return 0; if (f <= 0) {return 0;}
if (f === Infinity) return 1; if (f === Infinity) {return 1;}
const x = df2 / (df2 + df1 * f); const x = df2 / (df2 + df1 * f);
return 1 - betaIncomplete(df2 / 2, df1 / 2, x); return 1 - betaIncomplete(df2 / 2, df1 / 2, x);

View file

@ -55,7 +55,7 @@ export interface MarketRegime {
* Volume Weighted Average Price (VWAP) * Volume Weighted Average Price (VWAP)
*/ */
export function VWAP(ohlcv: OHLCVData[]): number[] { export function VWAP(ohlcv: OHLCVData[]): number[] {
if (ohlcv.length === 0) return []; if (ohlcv.length === 0) {return [];}
const vwap: number[] = []; const vwap: number[] = [];
let cumulativeVolumePrice = 0; let cumulativeVolumePrice = 0;
@ -76,7 +76,7 @@ export function VWAP(ohlcv: OHLCVData[]): number[] {
* Time Weighted Average Price (TWAP) * Time Weighted Average Price (TWAP)
*/ */
export function TWAP(prices: number[], timeWeights?: number[]): number { export function TWAP(prices: number[], timeWeights?: number[]): number {
if (prices.length === 0) return 0; if (prices.length === 0) {return 0;}
if (!timeWeights) { if (!timeWeights) {
return prices.reduce((sum, price) => sum + price, 0) / prices.length; return prices.reduce((sum, price) => sum + price, 0) / prices.length;
@ -227,9 +227,9 @@ export function identifyMarketRegime(
// Determine volatility level // Determine volatility level
let volatilityLevel: 'low' | 'medium' | 'high'; let volatilityLevel: 'low' | 'medium' | 'high';
if (volatility < 0.01) volatilityLevel = 'low'; if (volatility < 0.01) {volatilityLevel = 'low';}
else if (volatility < 0.03) volatilityLevel = 'medium'; else if (volatility < 0.03) {volatilityLevel = 'medium';}
else volatilityLevel = 'high'; else {volatilityLevel = 'high';}
// Determine regime // Determine regime
let regime: 'trending' | 'ranging' | 'volatile' | 'quiet'; let regime: 'trending' | 'ranging' | 'volatile' | 'quiet';
@ -281,7 +281,7 @@ export function OrderBookImbalance(
const totalVolume = totalBidVolume + totalAskVolume; const totalVolume = totalBidVolume + totalAskVolume;
if (totalVolume === 0) return 0; if (totalVolume === 0) {return 0;}
return (totalBidVolume - totalAskVolume) / totalVolume; return (totalBidVolume - totalAskVolume) / totalVolume;
} }
@ -452,10 +452,10 @@ export function MarketStress(
const overallStress = volatilityStress * 0.4 + liquidityStress * 0.3 + correlationStress * 0.3; const overallStress = volatilityStress * 0.4 + liquidityStress * 0.3 + correlationStress * 0.3;
let stressLevel: 'low' | 'medium' | 'high' | 'extreme'; let stressLevel: 'low' | 'medium' | 'high' | 'extreme';
if (overallStress < 0.25) stressLevel = 'low'; if (overallStress < 0.25) {stressLevel = 'low';}
else if (overallStress < 0.5) stressLevel = 'medium'; else if (overallStress < 0.5) {stressLevel = 'medium';}
else if (overallStress < 0.75) stressLevel = 'high'; else if (overallStress < 0.75) {stressLevel = 'high';}
else stressLevel = 'extreme'; else {stressLevel = 'extreme';}
return { return {
stressLevel, stressLevel,
@ -474,7 +474,7 @@ export function RealizedSpread(
midPrices: number[], midPrices: number[],
timeWindow: number = 5 // minutes timeWindow: number = 5 // minutes
): number { ): number {
if (trades.length === 0 || midPrices.length === 0) return 0; if (trades.length === 0 || midPrices.length === 0) {return 0;}
let totalSpread = 0; let totalSpread = 0;
let count = 0; let count = 0;
@ -541,7 +541,7 @@ export function ImplementationShortfall(
* Amihud Illiquidity Measure (price impact per unit of volume) * Amihud Illiquidity Measure (price impact per unit of volume)
*/ */
export function amihudIlliquidity(ohlcv: OHLCVData[], lookbackPeriod: number = 252): number { export function amihudIlliquidity(ohlcv: OHLCVData[], lookbackPeriod: number = 252): number {
if (ohlcv.length < lookbackPeriod) return 0; if (ohlcv.length < lookbackPeriod) {return 0;}
const recentData = ohlcv.slice(-lookbackPeriod); const recentData = ohlcv.slice(-lookbackPeriod);
let illiquiditySum = 0; let illiquiditySum = 0;
@ -566,7 +566,7 @@ export function amihudIlliquidity(ohlcv: OHLCVData[], lookbackPeriod: number = 2
* Roll's Spread Estimator (effective spread from serial covariance) * Roll's Spread Estimator (effective spread from serial covariance)
*/ */
export function rollSpreadEstimator(prices: number[]): number { export function rollSpreadEstimator(prices: number[]): number {
if (prices.length < 3) return 0; if (prices.length < 3) {return 0;}
// Calculate price changes // Calculate price changes
const priceChanges: number[] = []; const priceChanges: number[] = [];
@ -594,7 +594,7 @@ export function kyleLambda(
priceChanges: number[], priceChanges: number[],
orderFlow: number[] // Signed order flow (positive for buys, negative for sells) orderFlow: number[] // Signed order flow (positive for buys, negative for sells)
): number { ): number {
if (priceChanges.length !== orderFlow.length || priceChanges.length < 2) return 0; if (priceChanges.length !== orderFlow.length || priceChanges.length < 2) {return 0;}
// Calculate regression: priceChange = lambda * orderFlow + error // Calculate regression: priceChange = lambda * orderFlow + error
const n = priceChanges.length; const n = priceChanges.length;
@ -623,7 +623,7 @@ export function probabilityInformedTrading(
sellVolumes: number[], sellVolumes: number[],
period: number = 20 period: number = 20
): number { ): number {
if (buyVolumes.length !== sellVolumes.length || buyVolumes.length < period) return 0; if (buyVolumes.length !== sellVolumes.length || buyVolumes.length < period) {return 0;}
const recentBuys = buyVolumes.slice(-period); const recentBuys = buyVolumes.slice(-period);
const recentSells = sellVolumes.slice(-period); const recentSells = sellVolumes.slice(-period);
@ -647,11 +647,11 @@ export function probabilityInformedTrading(
* Herfindahl-Hirschman Index for Volume Concentration * Herfindahl-Hirschman Index for Volume Concentration
*/ */
export function volumeConcentrationHHI(exchanges: Array<{ name: string; volume: number }>): number { export function volumeConcentrationHHI(exchanges: Array<{ name: string; volume: number }>): number {
if (exchanges.length === 0) return 0; if (exchanges.length === 0) {return 0;}
const totalVolume = exchanges.reduce((sum, exchange) => sum + exchange.volume, 0); const totalVolume = exchanges.reduce((sum, exchange) => sum + exchange.volume, 0);
if (totalVolume === 0) return 0; if (totalVolume === 0) {return 0;}
let hhi = 0; let hhi = 0;
for (const exchange of exchanges) { for (const exchange of exchanges) {
@ -670,7 +670,7 @@ export function volumeProfile(
): { [price: number]: number } { ): { [price: number]: number } {
const profile: { [price: number]: number } = {}; const profile: { [price: number]: number } = {};
if (ohlcv.length === 0) return profile; if (ohlcv.length === 0) {return profile;}
const minPrice = Math.min(...ohlcv.map(candle => candle.low)); const minPrice = Math.min(...ohlcv.map(candle => candle.low));
const maxPrice = Math.max(...ohlcv.map(candle => candle.high)); const maxPrice = Math.max(...ohlcv.map(candle => candle.high));
@ -814,11 +814,11 @@ export function garmanKlassVolatility(
openPrices.length !== closePrices.length || openPrices.length !== closePrices.length ||
openPrices.length < 2 openPrices.length < 2
) )
return 0; {return 0;}
let sumSquaredTerm1 = 0; let sumSquaredTerm1 = 0;
let sumSquaredTerm2 = 0; let sumSquaredTerm2 = 0;
let sumSquaredTerm3 = 0; const sumSquaredTerm3 = 0;
for (let i = 0; i < openPrices.length; i++) { for (let i = 0; i < openPrices.length; i++) {
const logHO = Math.log(highPrices[i] / openPrices[i]); const logHO = Math.log(highPrices[i] / openPrices[i]);
@ -850,7 +850,7 @@ export function yangZhangVolatility(
openPrices.length !== previousClosePrices.length || openPrices.length !== previousClosePrices.length ||
openPrices.length < 2 openPrices.length < 2
) )
return 0; {return 0;}
const k = 0.34 / (1.34 + (openPrices.length + 1) / (previousClosePrices.length - 1)); const k = 0.34 / (1.34 + (openPrices.length + 1) / (previousClosePrices.length - 1));
@ -877,7 +877,7 @@ export function yangZhangVolatility(
* Volume Order Imbalance (VOI) * Volume Order Imbalance (VOI)
*/ */
export function volumeOrderImbalance(buyVolumes: number[], sellVolumes: number[]): number[] { export function volumeOrderImbalance(buyVolumes: number[], sellVolumes: number[]): number[] {
if (buyVolumes.length !== sellVolumes.length) return []; if (buyVolumes.length !== sellVolumes.length) {return [];}
const voi: number[] = []; const voi: number[] = [];
for (let i = 0; i < buyVolumes.length; i++) { for (let i = 0; i < buyVolumes.length; i++) {
@ -890,7 +890,7 @@ export function volumeOrderImbalance(buyVolumes: number[], sellVolumes: number[]
* Cumulative Volume Delta (CVD) * Cumulative Volume Delta (CVD)
*/ */
export function cumulativeVolumeDelta(buyVolumes: number[], sellVolumes: number[]): number[] { export function cumulativeVolumeDelta(buyVolumes: number[], sellVolumes: number[]): number[] {
if (buyVolumes.length !== sellVolumes.length) return []; if (buyVolumes.length !== sellVolumes.length) {return [];}
const cvd: number[] = []; const cvd: number[] = [];
let cumulativeDelta = 0; let cumulativeDelta = 0;
@ -905,7 +905,7 @@ export function cumulativeVolumeDelta(buyVolumes: number[], sellVolumes: number[
* Market Order Ratio * Market Order Ratio
*/ */
export function marketOrderRatio(marketOrders: number[], limitOrders: number[]): number[] { export function marketOrderRatio(marketOrders: number[], limitOrders: number[]): number[] {
if (marketOrders.length !== limitOrders.length) return []; if (marketOrders.length !== limitOrders.length) {return [];}
const ratios: number[] = []; const ratios: number[] = [];
for (let i = 0; i < marketOrders.length; i++) { for (let i = 0; i < marketOrders.length; i++) {
@ -920,12 +920,12 @@ export function marketOrderRatio(marketOrders: number[], limitOrders: number[]):
*/ */
function average(arr: number[]): number { function average(arr: number[]): number {
if (arr.length === 0) return 0; if (arr.length === 0) {return 0;}
return arr.reduce((a, b) => a + b, 0) / arr.length; return arr.reduce((a, b) => a + b, 0) / arr.length;
} }
function calculateVolatility(returns: number[]): number { function calculateVolatility(returns: number[]): number {
if (returns.length < 2) return 0; if (returns.length < 2) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
@ -935,7 +935,7 @@ function calculateVolatility(returns: number[]): number {
} }
function calculateCorrelation(x: number[], y: number[]): number { function calculateCorrelation(x: number[], y: number[]): number {
if (x.length !== y.length || x.length < 2) return 0; if (x.length !== y.length || x.length < 2) {return 0;}
const n = x.length; const n = x.length;
const meanX = x.reduce((sum, val) => sum + val, 0) / n; const meanX = x.reduce((sum, val) => sum + val, 0) / n;
@ -960,14 +960,14 @@ function calculateCorrelation(x: number[], y: number[]): number {
} }
function calculateVariance(values: number[]): number { function calculateVariance(values: number[]): number {
if (values.length < 2) return 0; if (values.length < 2) {return 0;}
const mean = values.reduce((sum, val) => sum + val, 0) / values.length; const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
return values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (values.length - 1); return values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (values.length - 1);
} }
function calculateCovariance(x: number[], y: number[]): number { function calculateCovariance(x: number[], y: number[]): number {
if (x.length !== y.length || x.length < 2) return 0; if (x.length !== y.length || x.length < 2) {return 0;}
const n = x.length; const n = x.length;
const meanX = x.reduce((sum, val) => sum + val, 0) / n; const meanX = x.reduce((sum, val) => sum + val, 0) / n;

View file

@ -605,7 +605,7 @@ function erf(x: number): number {
*/ */
function boxMullerTransform(): number { function boxMullerTransform(): number {
let u1 = Math.random(); let u1 = Math.random();
let u2 = Math.random(); const u2 = Math.random();
// Ensure u1 is not zero // Ensure u1 is not zero
while (u1 === 0) { while (u1 === 0) {

View file

@ -153,7 +153,7 @@ export function analyzeDrawdowns(
}> = []; }> = [];
let currentDrawdownStart: Date | null = null; let currentDrawdownStart: Date | null = null;
let drawdowns: number[] = []; const drawdowns: number[] = [];
for (let i = 1; i < equityCurve.length; i++) { for (let i = 1; i < equityCurve.length; i++) {
const current = equityCurve[i]; const current = equityCurve[i];
@ -297,7 +297,7 @@ export function calculateRollingMetrics(
windowSize: number, windowSize: number,
metricType: 'sharpe' | 'volatility' | 'return' = 'sharpe' metricType: 'sharpe' | 'volatility' | 'return' = 'sharpe'
): number[] { ): number[] {
if (returns.length < windowSize) return []; if (returns.length < windowSize) {return [];}
const rollingMetrics: number[] = []; const rollingMetrics: number[] = [];
@ -377,7 +377,7 @@ export function strategyPerformanceAttribution(
* Calculate Omega ratio * Calculate Omega ratio
*/ */
export function omegaRatio(returns: number[], threshold: number = 0): number { export function omegaRatio(returns: number[], threshold: number = 0): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const gains = returns const gains = returns
.filter(ret => ret > threshold) .filter(ret => ret > threshold)
@ -393,7 +393,7 @@ export function omegaRatio(returns: number[], threshold: number = 0): number {
* Calculate gain-to-pain ratio * Calculate gain-to-pain ratio
*/ */
export function gainToPainRatio(returns: number[]): number { export function gainToPainRatio(returns: number[]): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const totalGain = returns.reduce((sum, ret) => sum + ret, 0); const totalGain = returns.reduce((sum, ret) => sum + ret, 0);
const totalPain = returns.filter(ret => ret < 0).reduce((sum, ret) => sum + Math.abs(ret), 0); const totalPain = returns.filter(ret => ret < 0).reduce((sum, ret) => sum + Math.abs(ret), 0);
@ -405,12 +405,12 @@ export function gainToPainRatio(returns: number[]): number {
* Calculate Martin ratio (modified Sharpe with downside deviation) * Calculate Martin ratio (modified Sharpe with downside deviation)
*/ */
export function martinRatio(returns: number[], riskFreeRate: number = 0): number { export function martinRatio(returns: number[], riskFreeRate: number = 0): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const averageReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const averageReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const downsideReturns = returns.filter(ret => ret < riskFreeRate); const downsideReturns = returns.filter(ret => ret < riskFreeRate);
if (downsideReturns.length === 0) return Infinity; if (downsideReturns.length === 0) {return Infinity;}
const downsideDeviation = Math.sqrt( const downsideDeviation = Math.sqrt(
downsideReturns.reduce((sum, ret) => sum + Math.pow(ret - riskFreeRate, 2), 0) / returns.length downsideReturns.reduce((sum, ret) => sum + Math.pow(ret - riskFreeRate, 2), 0) / returns.length
@ -610,7 +610,7 @@ export function tailRatio(returns: number[], tailPercent: number = 0.1): number
const numReturns = returns.length; const numReturns = returns.length;
const tailSize = Math.floor(numReturns * tailPercent); const tailSize = Math.floor(numReturns * tailPercent);
if (tailSize === 0) return 0; if (tailSize === 0) {return 0;}
const sortedReturns = [...returns].sort((a, b) => a - b); const sortedReturns = [...returns].sort((a, b) => a - b);
const worstTail = sortedReturns.slice(0, tailSize); const worstTail = sortedReturns.slice(0, tailSize);
@ -631,7 +631,7 @@ export function calculateRollingBeta(
windowSize: number windowSize: number
): number[] { ): number[] {
if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize) if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize)
return []; {return [];}
const rollingBetas: number[] = []; const rollingBetas: number[] = [];
@ -668,7 +668,7 @@ export function calculateRollingAlpha(
windowSize: number windowSize: number
): number[] { ): number[] {
if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize) if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize)
return []; {return [];}
const rollingAlphas: number[] = []; const rollingAlphas: number[] = [];
@ -728,7 +728,7 @@ export function moneyWeightedRateOfReturn(
// Helper functions // Helper functions
function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0): number { function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0): number {
if (returns.length < 2) return 0; if (returns.length < 2) {return 0;}
const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
@ -739,7 +739,7 @@ function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0): numb
} }
function calculateVolatility(returns: number[]): number { function calculateVolatility(returns: number[]): number {
if (returns.length < 2) return 0; if (returns.length < 2) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
@ -749,7 +749,7 @@ function calculateVolatility(returns: number[]): number {
} }
function calculateBeta(portfolioReturns: number[], marketReturns: number[]): number { function calculateBeta(portfolioReturns: number[], marketReturns: number[]): number {
if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < 2) return 0; if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < 2) {return 0;}
const portfolioMean = const portfolioMean =
portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length; portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
@ -786,13 +786,13 @@ function calculateAlpha(
} }
function calculateSkewness(returns: number[]): number { function calculateSkewness(returns: number[]): number {
if (returns.length < 3) return 0; if (returns.length < 3) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
const stdDev = Math.sqrt(variance); const stdDev = Math.sqrt(variance);
if (stdDev === 0) return 0; if (stdDev === 0) {return 0;}
const skew = const skew =
returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 3), 0) / returns.length; returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 3), 0) / returns.length;
@ -801,13 +801,13 @@ function calculateSkewness(returns: number[]): number {
} }
function calculateKurtosis(returns: number[]): number { function calculateKurtosis(returns: number[]): number {
if (returns.length < 4) return 0; if (returns.length < 4) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
const stdDev = Math.sqrt(variance); const stdDev = Math.sqrt(variance);
if (stdDev === 0) return 0; if (stdDev === 0) {return 0;}
const kurt = const kurt =
returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 4), 0) / returns.length; returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 4), 0) / returns.length;

View file

@ -209,7 +209,7 @@ export function riskParityOptimization(covarianceMatrix: number[][]): PortfolioO
const sum = newWeights.reduce((s, w) => s + w, 0); const sum = newWeights.reduce((s, w) => s + w, 0);
weights = newWeights.map(w => w / sum); weights = newWeights.map(w => w / sum);
if (converged) break; if (converged) {break;}
} }
const portfolioVariance = calculatePortfolioVariance(weights, covarianceMatrix); const portfolioVariance = calculatePortfolioVariance(weights, covarianceMatrix);
@ -402,7 +402,7 @@ export function calculateEfficientFrontier(
volatility: number; volatility: number;
sharpeRatio: number; sharpeRatio: number;
}> { }> {
if (returns.length !== symbols.length || returns.length < 2) return []; if (returns.length !== symbols.length || returns.length < 2) {return [];}
const n = returns.length; const n = returns.length;
const results: Array<{ const results: Array<{
@ -456,7 +456,7 @@ export function findMinimumVariancePortfolio(
returns: number[][], returns: number[][],
symbols: string[] symbols: string[]
): PortfolioOptimizationResult | null { ): PortfolioOptimizationResult | null {
if (returns.length !== symbols.length || returns.length < 2) return null; if (returns.length !== symbols.length || returns.length < 2) {return null;}
const covarianceMatrix = calculateCovarianceMatrix(returns); const covarianceMatrix = calculateCovarianceMatrix(returns);
const n = returns.length; const n = returns.length;
@ -517,7 +517,7 @@ function calculateCovarianceMatrix(returns: number[][]): number[][] {
} }
function calculateCovariance(x: number[], y: number[]): number { function calculateCovariance(x: number[], y: number[]): number {
if (x.length !== y.length || x.length < 2) return 0; if (x.length !== y.length || x.length < 2) {return 0;}
const n = x.length; const n = x.length;
const meanX = x.reduce((sum, val) => sum + val, 0) / n; const meanX = x.reduce((sum, val) => sum + val, 0) / n;
@ -559,7 +559,7 @@ function findMinimumVarianceWeights(
const currentReturn = weights.reduce((sum, w, i) => sum + w * expectedReturns[i], 0); const currentReturn = weights.reduce((sum, w, i) => sum + w * expectedReturns[i], 0);
const returnDiff = targetReturn - currentReturn; const returnDiff = targetReturn - currentReturn;
if (Math.abs(returnDiff) < 0.001) break; if (Math.abs(returnDiff) < 0.001) {break;}
// Adjust weights proportionally to expected returns // Adjust weights proportionally to expected returns
const totalExpectedReturn = expectedReturns.reduce((sum, r) => sum + Math.abs(r), 0); const totalExpectedReturn = expectedReturns.reduce((sum, r) => sum + Math.abs(r), 0);

View file

@ -31,8 +31,8 @@ export function fixedRiskPositionSize(params: PositionSizeParams): number {
const { accountSize, riskPercentage, entryPrice, stopLoss, leverage = 1 } = params; const { accountSize, riskPercentage, entryPrice, stopLoss, leverage = 1 } = params;
// Input validation // Input validation
if (accountSize <= 0 || riskPercentage <= 0 || entryPrice <= 0 || leverage <= 0) return 0; if (accountSize <= 0 || riskPercentage <= 0 || entryPrice <= 0 || leverage <= 0) {return 0;}
if (entryPrice === stopLoss) return 0; if (entryPrice === stopLoss) {return 0;}
const riskAmount = accountSize * (riskPercentage / 100); const riskAmount = accountSize * (riskPercentage / 100);
const riskPerShare = Math.abs(entryPrice - stopLoss); const riskPerShare = Math.abs(entryPrice - stopLoss);
@ -48,7 +48,7 @@ export function kellyPositionSize(params: KellyParams, accountSize: number): num
const { winRate, averageWin, averageLoss } = params; const { winRate, averageWin, averageLoss } = params;
// Validate inputs // Validate inputs
if (averageLoss === 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0) return 0; if (averageLoss === 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0) {return 0;}
const lossRate = 1 - winRate; const lossRate = 1 - winRate;
const winLossRatio = averageWin / Math.abs(averageLoss); const winLossRatio = averageWin / Math.abs(averageLoss);
@ -72,7 +72,7 @@ export function fractionalKellyPositionSize(
fraction: number = 0.25 fraction: number = 0.25
): number { ): number {
// Input validation // Input validation
if (fraction <= 0 || fraction > 1) return 0; if (fraction <= 0 || fraction > 1) {return 0;}
const fullKelly = kellyPositionSize(params, accountSize); const fullKelly = kellyPositionSize(params, accountSize);
return fullKelly * fraction; return fullKelly * fraction;
@ -88,7 +88,7 @@ export function volatilityTargetPositionSize(
const { price, volatility, targetVolatility } = params; const { price, volatility, targetVolatility } = params;
// Input validation // Input validation
if (volatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) return 0; if (volatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {return 0;}
const volatilityRatio = targetVolatility / volatility; const volatilityRatio = targetVolatility / volatility;
const basePositionValue = accountSize * Math.min(volatilityRatio, 2); // Cap at 2x leverage const basePositionValue = accountSize * Math.min(volatilityRatio, 2); // Cap at 2x leverage
@ -105,7 +105,7 @@ export function equalWeightPositionSize(
price: number price: number
): number { ): number {
// Input validation // Input validation
if (numberOfPositions <= 0 || price <= 0 || accountSize <= 0) return 0; if (numberOfPositions <= 0 || price <= 0 || accountSize <= 0) {return 0;}
const positionValue = accountSize / numberOfPositions; const positionValue = accountSize / numberOfPositions;
return Math.floor(positionValue / price); return Math.floor(positionValue / price);
@ -121,7 +121,7 @@ export function atrBasedPositionSize(
atrMultiplier: number = 2, atrMultiplier: number = 2,
price: number price: number
): number { ): number {
if (atrValue === 0 || price === 0) return 0; if (atrValue === 0 || price === 0) {return 0;}
const riskAmount = accountSize * (riskPercentage / 100); const riskAmount = accountSize * (riskPercentage / 100);
const stopDistance = atrValue * atrMultiplier; const stopDistance = atrValue * atrMultiplier;
@ -143,11 +143,11 @@ export function expectancyPositionSize(
): number { ): number {
// Input validation // Input validation
if (accountSize <= 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0 || averageLoss === 0) if (accountSize <= 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0 || averageLoss === 0)
return 0; {return 0;}
const expectancy = winRate * averageWin - (1 - winRate) * Math.abs(averageLoss); const expectancy = winRate * averageWin - (1 - winRate) * Math.abs(averageLoss);
if (expectancy <= 0) return 0; if (expectancy <= 0) {return 0;}
// Scale position size based on expectancy relative to average loss // Scale position size based on expectancy relative to average loss
// Higher expectancy relative to risk allows for larger position // Higher expectancy relative to risk allows for larger position
@ -167,7 +167,7 @@ export function monteCarloPositionSize(
simulations: number = 1000, simulations: number = 1000,
confidenceLevel: number = 0.95 confidenceLevel: number = 0.95
): number { ): number {
if (historicalReturns.length === 0) return 0; if (historicalReturns.length === 0) {return 0;}
const outcomes: number[] = []; const outcomes: number[] = [];
const mean = historicalReturns.reduce((sum, ret) => sum + ret, 0) / historicalReturns.length; const mean = historicalReturns.reduce((sum, ret) => sum + ret, 0) / historicalReturns.length;
@ -230,7 +230,7 @@ export function sharpeOptimizedPositionSize(
): number { ): number {
// Input validation // Input validation
if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0) if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0)
return 0; {return 0;}
// Kelly criterion with Sharpe ratio optimization // Kelly criterion with Sharpe ratio optimization
const excessReturn = expectedReturn - riskFreeRate; const excessReturn = expectedReturn - riskFreeRate;
const kellyFraction = excessReturn / (volatility * volatility); const kellyFraction = excessReturn / (volatility * volatility);
@ -251,7 +251,7 @@ export function fixedFractionalPositionSize(
price: number price: number
): number { ): number {
// Input validation // Input validation
if (stopLossPercentage <= 0 || price <= 0 || riskPercentage <= 0 || accountSize <= 0) return 0; if (stopLossPercentage <= 0 || price <= 0 || riskPercentage <= 0 || accountSize <= 0) {return 0;}
const riskAmount = accountSize * (riskPercentage / 100); const riskAmount = accountSize * (riskPercentage / 100);
const stopLossAmount = price * (stopLossPercentage / 100); const stopLossAmount = price * (stopLossPercentage / 100);
@ -269,7 +269,7 @@ export function volatilityAdjustedPositionSize(
price: number price: number
): number { ): number {
// Input validation // Input validation
if (assetVolatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) return 0; if (assetVolatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {return 0;}
const volatilityRatio = targetVolatility / assetVolatility; const volatilityRatio = targetVolatility / assetVolatility;
const cappedRatio = Math.min(volatilityRatio, 3); // Cap at 3x leverage const cappedRatio = Math.min(volatilityRatio, 3); // Cap at 3x leverage
@ -286,7 +286,7 @@ export function correlationAdjustedPositionSize(
existingPositions: Array<{ size: number; correlation: number }>, existingPositions: Array<{ size: number; correlation: number }>,
maxCorrelationRisk: number = 0.3 maxCorrelationRisk: number = 0.3
): number { ): number {
if (existingPositions.length === 0 || basePositionSize <= 0) return basePositionSize; if (existingPositions.length === 0 || basePositionSize <= 0) {return basePositionSize;}
// Calculate portfolio correlation risk // Calculate portfolio correlation risk
// This should consider the correlation between the new position and existing ones // This should consider the correlation between the new position and existing ones
@ -310,7 +310,7 @@ export function calculatePortfolioHeat(
accountSize: number accountSize: number
): number { ): number {
// Input validation // Input validation
if (accountSize <= 0 || positions.length === 0) return 0; if (accountSize <= 0 || positions.length === 0) {return 0;}
const totalRisk = positions.reduce((sum, position) => { const totalRisk = positions.reduce((sum, position) => {
// Ensure risk values are positive // Ensure risk values are positive
@ -331,8 +331,8 @@ export function dynamicPositionSize(
maxDrawdownThreshold: number = 0.1 maxDrawdownThreshold: number = 0.1
): number { ): number {
// Input validation // Input validation
if (basePositionSize <= 0 || marketVolatility <= 0 || normalVolatility <= 0) return 0; if (basePositionSize <= 0 || marketVolatility <= 0 || normalVolatility <= 0) {return 0;}
if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) return basePositionSize; if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) {return basePositionSize;}
// Volatility adjustment - reduce size when volatility is high // Volatility adjustment - reduce size when volatility is high
const volatilityAdjustment = Math.min(normalVolatility / marketVolatility, 2); // Cap at 2x const volatilityAdjustment = Math.min(normalVolatility / marketVolatility, 2); // Cap at 2x
@ -354,7 +354,7 @@ export function liquidityConstrainedPositionSize(
maxVolumePercentage: number = 0.05, maxVolumePercentage: number = 0.05,
price: number price: number
): number { ): number {
if (averageDailyVolume === 0 || price === 0) return 0; if (averageDailyVolume === 0 || price === 0) {return 0;}
const maxShares = averageDailyVolume * maxVolumePercentage; const maxShares = averageDailyVolume * maxVolumePercentage;
@ -372,7 +372,7 @@ export function multiTimeframePositionSize(
baseRiskPercentage: number = 1 baseRiskPercentage: number = 1
): number { ): number {
// Input validation // Input validation
if (accountSize <= 0 || baseRiskPercentage <= 0) return 0; if (accountSize <= 0 || baseRiskPercentage <= 0) {return 0;}
// Clamp signals to valid range // Clamp signals to valid range
const clampedShort = Math.max(-1, Math.min(1, shortTermSignal)); const clampedShort = Math.max(-1, Math.min(1, shortTermSignal));
@ -396,18 +396,18 @@ export function riskParityPositionSize(
targetRisk: number, targetRisk: number,
accountSize: number accountSize: number
): number[] { ): number[] {
if (assets.length === 0) return []; if (assets.length === 0) {return [];}
// Calculate inverse volatility weights // Calculate inverse volatility weights
const totalInverseVol = assets.reduce((sum, asset) => { const totalInverseVol = assets.reduce((sum, asset) => {
if (asset.volatility === 0) return sum; if (asset.volatility === 0) {return sum;}
return sum + 1 / asset.volatility; return sum + 1 / asset.volatility;
}, 0); }, 0);
if (totalInverseVol === 0) return assets.map(() => 0); if (totalInverseVol === 0) {return assets.map(() => 0);}
return assets.map(asset => { return assets.map(asset => {
if (asset.volatility === 0 || asset.price === 0) return 0; if (asset.volatility === 0 || asset.price === 0) {return 0;}
// Calculate weight based on inverse volatility // Calculate weight based on inverse volatility
const weight = 1 / asset.volatility / totalInverseVol; const weight = 1 / asset.volatility / totalInverseVol;
@ -468,7 +468,7 @@ export function optimalFPositionSize(
historicalReturns: number[], historicalReturns: number[],
maxIterations: number = 100 maxIterations: number = 100
): number { ): number {
if (historicalReturns.length === 0 || accountSize <= 0) return 0; if (historicalReturns.length === 0 || accountSize <= 0) {return 0;}
// Convert returns to P&L per unit // Convert returns to P&L per unit
const pnlValues = historicalReturns.map(ret => ret * 1000); // Assuming $1000 per unit const pnlValues = historicalReturns.map(ret => ret * 1000); // Assuming $1000 per unit
@ -512,7 +512,7 @@ export function secureFPositionSize(
historicalReturns: number[], historicalReturns: number[],
confidenceLevel: number = 0.95 confidenceLevel: number = 0.95
): number { ): number {
if (historicalReturns.length === 0 || accountSize <= 0) return 0; if (historicalReturns.length === 0 || accountSize <= 0) {return 0;}
// Sort returns to find worst-case scenarios // Sort returns to find worst-case scenarios
const sortedReturns = [...historicalReturns].sort((a, b) => a - b); const sortedReturns = [...historicalReturns].sort((a, b) => a - b);
@ -523,7 +523,7 @@ export function secureFPositionSize(
const maxLoss = Math.abs(worstCaseReturn); const maxLoss = Math.abs(worstCaseReturn);
const maxRiskPercentage = 0.02; // Never risk more than 2% on worst case const maxRiskPercentage = 0.02; // Never risk more than 2% on worst case
if (maxLoss === 0) return accountSize * 0.1; // Default to 10% if no historical losses if (maxLoss === 0) {return accountSize * 0.1;} // Default to 10% if no historical losses
const secureF = Math.min(maxRiskPercentage / maxLoss, 0.25); // Cap at 25% const secureF = Math.min(maxRiskPercentage / maxLoss, 0.25); // Cap at 25%

View file

@ -9,7 +9,7 @@ import { RiskMetrics, treynorRatio } from './index';
* Calculate Value at Risk (VaR) using historical simulation * Calculate Value at Risk (VaR) using historical simulation
*/ */
export function valueAtRisk(returns: number[], confidenceLevel: number = 0.95): number { export function valueAtRisk(returns: number[], confidenceLevel: number = 0.95): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const sortedReturns = [...returns].sort((a, b) => a - b); const sortedReturns = [...returns].sort((a, b) => a - b);
const index = Math.floor((1 - confidenceLevel) * sortedReturns.length); const index = Math.floor((1 - confidenceLevel) * sortedReturns.length);
@ -21,12 +21,12 @@ export function valueAtRisk(returns: number[], confidenceLevel: number = 0.95):
* Calculate Conditional Value at Risk (CVaR/Expected Shortfall) * Calculate Conditional Value at Risk (CVaR/Expected Shortfall)
*/ */
export function conditionalValueAtRisk(returns: number[], confidenceLevel: number = 0.95): number { export function conditionalValueAtRisk(returns: number[], confidenceLevel: number = 0.95): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const sortedReturns = [...returns].sort((a, b) => a - b); const sortedReturns = [...returns].sort((a, b) => a - b);
const cutoffIndex = Math.floor((1 - confidenceLevel) * sortedReturns.length); const cutoffIndex = Math.floor((1 - confidenceLevel) * sortedReturns.length);
if (cutoffIndex === 0) return sortedReturns[0]; if (cutoffIndex === 0) {return sortedReturns[0];}
const tailReturns = sortedReturns.slice(0, cutoffIndex); const tailReturns = sortedReturns.slice(0, cutoffIndex);
return tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length; return tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length;
@ -40,7 +40,7 @@ export function parametricVaR(
confidenceLevel: number = 0.95, confidenceLevel: number = 0.95,
portfolioValue: number = 1 portfolioValue: number = 1
): number { ): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
@ -57,7 +57,7 @@ export function parametricVaR(
* Calculate maximum drawdown * Calculate maximum drawdown
*/ */
export function maxDrawdown(equityCurve: number[]): number { export function maxDrawdown(equityCurve: number[]): number {
if (equityCurve.length < 2) return 0; if (equityCurve.length < 2) {return 0;}
let maxDD = 0; let maxDD = 0;
let peak = equityCurve[0]; let peak = equityCurve[0];
@ -78,11 +78,11 @@ export function maxDrawdown(equityCurve: number[]): number {
* Calculate downside deviation * Calculate downside deviation
*/ */
export function downsideDeviation(returns: number[], targetReturn: number = 0): number { export function downsideDeviation(returns: number[], targetReturn: number = 0): number {
if (returns.length === 0) return 0; if (returns.length === 0) {return 0;}
const downsideReturns = returns.filter(ret => ret < targetReturn); const downsideReturns = returns.filter(ret => ret < targetReturn);
if (downsideReturns.length === 0) return 0; if (downsideReturns.length === 0) {return 0;}
const sumSquaredDownside = downsideReturns.reduce( const sumSquaredDownside = downsideReturns.reduce(
(sum, ret) => sum + Math.pow(ret - targetReturn, 2), (sum, ret) => sum + Math.pow(ret - targetReturn, 2),
@ -96,14 +96,14 @@ export function downsideDeviation(returns: number[], targetReturn: number = 0):
* Calculate Sharpe ratio * Calculate Sharpe ratio
*/ */
export function sharpeRatio(returns: number[], riskFreeRate: number = 0): number { export function sharpeRatio(returns: number[], riskFreeRate: number = 0): number {
if (returns.length < 2) return 0; if (returns.length < 2) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (returns.length - 1); returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (returns.length - 1);
const stdDev = Math.sqrt(variance); const stdDev = Math.sqrt(variance);
if (stdDev === 0) return 0; if (stdDev === 0) {return 0;}
return (mean - riskFreeRate) / stdDev; return (mean - riskFreeRate) / stdDev;
} }
@ -172,7 +172,7 @@ export function trackingError(portfolioReturns: number[], benchmarkReturns: numb
* Calculate volatility (standard deviation of returns) * Calculate volatility (standard deviation of returns)
*/ */
export function volatility(returns: number[]): number { export function volatility(returns: number[]): number {
if (returns.length < 2) return 0; if (returns.length < 2) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = const variance =
@ -192,13 +192,13 @@ export function annualizedVolatility(returns: number[], periodsPerYear: number =
* Calculate skewness (measure of asymmetry) * Calculate skewness (measure of asymmetry)
*/ */
export function skewness(returns: number[]): number { export function skewness(returns: number[]): number {
if (returns.length < 3) return 0; if (returns.length < 3) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
const stdDev = Math.sqrt(variance); const stdDev = Math.sqrt(variance);
if (stdDev === 0) return 0; if (stdDev === 0) {return 0;}
const skew = const skew =
returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 3), 0) / returns.length; returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 3), 0) / returns.length;
@ -210,13 +210,13 @@ export function skewness(returns: number[]): number {
* Calculate kurtosis (measure of tail heaviness) * Calculate kurtosis (measure of tail heaviness)
*/ */
export function kurtosis(returns: number[]): number { export function kurtosis(returns: number[]): number {
if (returns.length < 4) return 0; if (returns.length < 4) {return 0;}
const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
const stdDev = Math.sqrt(variance); const stdDev = Math.sqrt(variance);
if (stdDev === 0) return 0; if (stdDev === 0) {return 0;}
const kurt = const kurt =
returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 4), 0) / returns.length; returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 4), 0) / returns.length;
@ -317,12 +317,12 @@ function getZScore(confidenceLevel: number): number {
}; };
const key = confidenceLevel.toString(); const key = confidenceLevel.toString();
if (zScores[key]) return zScores[key]; if (zScores[key]) {return zScores[key];}
// For arbitrary confidence levels, use approximation // For arbitrary confidence levels, use approximation
if (confidenceLevel < 0.5) return -getZScore(1 - confidenceLevel); if (confidenceLevel < 0.5) {return -getZScore(1 - confidenceLevel);}
if (confidenceLevel >= 0.999) return 3.09; // Cap at 99.9% for numerical stability if (confidenceLevel >= 0.999) {return 3.09;} // Cap at 99.9% for numerical stability
// Approximation of inverse normal CDF // Approximation of inverse normal CDF
const y = Math.sqrt(-2.0 * Math.log(1.0 - confidenceLevel)); const y = Math.sqrt(-2.0 * Math.log(1.0 - confidenceLevel));
@ -382,6 +382,6 @@ export function riskAdjustedReturn(
portfolioRisk: number, portfolioRisk: number,
riskFreeRate: number = 0 riskFreeRate: number = 0
): number { ): number {
if (portfolioRisk === 0) return 0; if (portfolioRisk === 0) {return 0;}
return (portfolioReturn - riskFreeRate) / portfolioRisk; return (portfolioReturn - riskFreeRate) / portfolioRisk;
} }

View file

@ -9,7 +9,7 @@ import { OHLCVData } from './index';
* Simple Moving Average * Simple Moving Average
*/ */
export function sma(values: number[], period: number): number[] { export function sma(values: number[], period: number): number[] {
if (period > values.length) return []; if (period > values.length) {return [];}
const result: number[] = []; const result: number[] = [];
@ -25,7 +25,7 @@ export function sma(values: number[], period: number): number[] {
* Exponential Moving Average * Exponential Moving Average
*/ */
export function ema(values: number[], period: number): number[] { export function ema(values: number[], period: number): number[] {
if (period > values.length) return []; if (period > values.length) {return [];}
const result: number[] = []; const result: number[] = [];
const multiplier = 2 / (period + 1); const multiplier = 2 / (period + 1);
@ -46,7 +46,7 @@ export function ema(values: number[], period: number): number[] {
* Relative Strength Index (RSI) * Relative Strength Index (RSI)
*/ */
export function rsi(prices: number[], period: number = 14): number[] { export function rsi(prices: number[], period: number = 14): number[] {
if (period >= prices.length) return []; if (period >= prices.length) {return [];}
const gains: number[] = []; const gains: number[] = [];
const losses: number[] = []; const losses: number[] = [];
@ -141,7 +141,7 @@ export function bollingerBands(
* Average True Range (ATR) * Average True Range (ATR)
*/ */
export function atr(ohlcv: OHLCVData[], period: number = 14): number[] { export function atr(ohlcv: OHLCVData[], period: number = 14): number[] {
if (period >= ohlcv.length) return []; if (period >= ohlcv.length) {return [];}
const trueRanges: number[] = []; const trueRanges: number[] = [];
@ -166,7 +166,7 @@ export function stochastic(
kPeriod: number = 14, kPeriod: number = 14,
dPeriod: number = 3 dPeriod: number = 3
): { k: number[]; d: number[] } { ): { k: number[]; d: number[] } {
if (kPeriod >= ohlcv.length) return { k: [], d: [] }; if (kPeriod >= ohlcv.length) {return { k: [], d: [] };}
const kValues: number[] = []; const kValues: number[] = [];
@ -193,7 +193,7 @@ export function stochastic(
* Williams %R * Williams %R
*/ */
export function williamsR(ohlcv: OHLCVData[], period: number = 14): number[] { export function williamsR(ohlcv: OHLCVData[], period: number = 14): number[] {
if (period >= ohlcv.length) return []; if (period >= ohlcv.length) {return [];}
const result: number[] = []; const result: number[] = [];
@ -218,7 +218,7 @@ export function williamsR(ohlcv: OHLCVData[], period: number = 14): number[] {
* Commodity Channel Index (CCI) * Commodity Channel Index (CCI)
*/ */
export function cci(ohlcv: OHLCVData[], period: number = 20): number[] { export function cci(ohlcv: OHLCVData[], period: number = 20): number[] {
if (period >= ohlcv.length) return []; if (period >= ohlcv.length) {return [];}
const typicalPrices = ohlcv.map(d => (d.high + d.low + d.close) / 3); const typicalPrices = ohlcv.map(d => (d.high + d.low + d.close) / 3);
const smaTP = sma(typicalPrices, period); const smaTP = sma(typicalPrices, period);
@ -244,7 +244,7 @@ export function cci(ohlcv: OHLCVData[], period: number = 20): number[] {
* Momentum * Momentum
*/ */
export function momentum(prices: number[], period: number = 10): number[] { export function momentum(prices: number[], period: number = 10): number[] {
if (period >= prices.length) return []; if (period >= prices.length) {return [];}
const result: number[] = []; const result: number[] = [];
@ -260,7 +260,7 @@ export function momentum(prices: number[], period: number = 10): number[] {
* Rate of Change (ROC) * Rate of Change (ROC)
*/ */
export function roc(prices: number[], period: number = 10): number[] { export function roc(prices: number[], period: number = 10): number[] {
if (period >= prices.length) return []; if (period >= prices.length) {return [];}
const result: number[] = []; const result: number[] = [];
@ -280,7 +280,7 @@ export function roc(prices: number[], period: number = 10): number[] {
* Money Flow Index (MFI) * Money Flow Index (MFI)
*/ */
export function mfi(ohlcv: OHLCVData[], period: number = 14): number[] { export function mfi(ohlcv: OHLCVData[], period: number = 14): number[] {
if (period >= ohlcv.length) return []; if (period >= ohlcv.length) {return [];}
const typicalPrices = ohlcv.map(d => (d.high + d.low + d.close) / 3); const typicalPrices = ohlcv.map(d => (d.high + d.low + d.close) / 3);
const moneyFlows = ohlcv.map((d, i) => typicalPrices[i] * d.volume); const moneyFlows = ohlcv.map((d, i) => typicalPrices[i] * d.volume);
@ -317,7 +317,7 @@ export function mfi(ohlcv: OHLCVData[], period: number = 14): number[] {
* On-Balance Volume (OBV) * On-Balance Volume (OBV)
*/ */
export function obv(ohlcv: OHLCVData[]): number[] { export function obv(ohlcv: OHLCVData[]): number[] {
if (ohlcv.length === 0) return []; if (ohlcv.length === 0) {return [];}
const result: number[] = [ohlcv[0].volume]; const result: number[] = [ohlcv[0].volume];
@ -341,7 +341,7 @@ export function obv(ohlcv: OHLCVData[]): number[] {
* Accumulation/Distribution Line * Accumulation/Distribution Line
*/ */
export function accumulationDistribution(ohlcv: OHLCVData[]): number[] { export function accumulationDistribution(ohlcv: OHLCVData[]): number[] {
if (ohlcv.length === 0) return []; if (ohlcv.length === 0) {return [];}
const result: number[] = []; const result: number[] = [];
let adLine = 0; let adLine = 0;
@ -367,7 +367,7 @@ export function accumulationDistribution(ohlcv: OHLCVData[]): number[] {
* Chaikin Money Flow (CMF) * Chaikin Money Flow (CMF)
*/ */
export function chaikinMoneyFlow(ohlcv: OHLCVData[], period: number = 20): number[] { export function chaikinMoneyFlow(ohlcv: OHLCVData[], period: number = 20): number[] {
if (period >= ohlcv.length) return []; if (period >= ohlcv.length) {return [];}
const adValues: number[] = []; const adValues: number[] = [];
@ -406,7 +406,7 @@ export function parabolicSAR(
step: number = 0.02, step: number = 0.02,
maxStep: number = 0.2 maxStep: number = 0.2
): number[] { ): number[] {
if (ohlcv.length < 2) return []; if (ohlcv.length < 2) {return [];}
const result: number[] = []; const result: number[] = [];
let trend = 1; // 1 for uptrend, -1 for downtrend let trend = 1; // 1 for uptrend, -1 for downtrend
@ -467,7 +467,7 @@ export function parabolicSAR(
* Aroon Indicator * Aroon Indicator
*/ */
export function aroon(ohlcv: OHLCVData[], period: number = 14): { up: number[]; down: number[] } { export function aroon(ohlcv: OHLCVData[], period: number = 14): { up: number[]; down: number[] } {
if (period >= ohlcv.length) return { up: [], down: [] }; if (period >= ohlcv.length) {return { up: [], down: [] };}
const up: number[] = []; const up: number[] = [];
const down: number[] = []; const down: number[] = [];
@ -505,7 +505,7 @@ export function adx(
ohlcv: OHLCVData[], ohlcv: OHLCVData[],
period: number = 14 period: number = 14
): { adx: number[]; plusDI: number[]; minusDI: number[] } { ): { adx: number[]; plusDI: number[]; minusDI: number[] } {
if (period >= ohlcv.length) return { adx: [], plusDI: [], minusDI: [] }; if (period >= ohlcv.length) {return { adx: [], plusDI: [], minusDI: [] };}
const trueRanges: number[] = []; const trueRanges: number[] = [];
const plusDM: number[] = []; const plusDM: number[] = [];
@ -572,7 +572,7 @@ export function adx(
* Volume Weighted Moving Average (VWMA) * Volume Weighted Moving Average (VWMA)
*/ */
export function vwma(ohlcv: OHLCVData[], period: number = 20): number[] { export function vwma(ohlcv: OHLCVData[], period: number = 20): number[] {
if (period >= ohlcv.length) return []; if (period >= ohlcv.length) {return [];}
const result: number[] = []; const result: number[] = [];
@ -607,7 +607,7 @@ export function pivotPoints(ohlcv: OHLCVData[]): Array<{
support2: number; support2: number;
support3: number; support3: number;
}> { }> {
if (ohlcv.length === 0) return []; if (ohlcv.length === 0) {return [];}
const result: Array<{ const result: Array<{
pivot: number; pivot: number;

View file

@ -242,7 +242,7 @@ export function identifyVolatilityRegimes(
// Classify returns into regimes // Classify returns into regimes
const regimeSequence = absReturns.map(absRet => { const regimeSequence = absReturns.map(absRet => {
for (let i = 0; i < thresholds.length; i++) { for (let i = 0; i < thresholds.length; i++) {
if (absRet <= thresholds[i]) return i; if (absRet <= thresholds[i]) {return i;}
} }
return numRegimes - 1; return numRegimes - 1;
}); });
@ -537,7 +537,7 @@ export function calculateYangZhangVolatility(
* Parkinson volatility estimator * Parkinson volatility estimator
*/ */
export function parkinsonVolatility(ohlcv: OHLCVData[], annualizationFactor: number = 252): number { export function parkinsonVolatility(ohlcv: OHLCVData[], annualizationFactor: number = 252): number {
if (ohlcv.length < 2) return 0; if (ohlcv.length < 2) {return 0;}
const sum = ohlcv.slice(1).reduce((acc, curr) => { const sum = ohlcv.slice(1).reduce((acc, curr) => {
const range = Math.log(curr.high / curr.low); const range = Math.log(curr.high / curr.low);
return acc + range * range; return acc + range * range;

View file

@ -5,9 +5,5 @@
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"references": [ "references": [{ "path": "../types" }, { "path": "../config" }, { "path": "../logger" }]
{ "path": "../types" },
{ "path": "../config" },
{ "path": "../logger" }
]
} }

View file

@ -326,7 +326,7 @@ export class VectorEngine {
let peak = equity[0]; let peak = equity[0];
for (const eq of equity) { for (const eq of equity) {
if (eq > peak) peak = eq; if (eq > peak) {peak = eq;}
drawdown.push((peak - eq) / peak); drawdown.push((peak - eq) / peak);
} }

View file

@ -18,7 +18,11 @@
"test:e2e": "bun test test/e2e", "test:e2e": "bun test test/e2e",
"test:libs": "turbo run test --filter='./libs/*'", "test:libs": "turbo run test --filter='./libs/*'",
"test:apps": "turbo run test --filter=./apps/*/*", "test:apps": "turbo run test --filter=./apps/*/*",
"lint": "turbo run lint", "lint": "eslint apps libs --ext .ts,.tsx",
"lint:fix": "eslint apps libs --ext .ts,.tsx --fix",
"lint:check": "eslint apps libs --ext .ts,.tsx --max-warnings 0",
"lint:apps": "eslint apps --ext .ts,.tsx",
"lint:libs": "eslint libs --ext .ts,.tsx",
"format": "./scripts/format.sh", "format": "./scripts/format.sh",
"format:check": "prettier --check 'apps/**/*.{ts,json}' 'libs/**/*.{ts,json}' '*.json'", "format:check": "prettier --check 'apps/**/*.{ts,json}' 'libs/**/*.{ts,json}' '*.json'",
"format:ts": "prettier --write 'apps/**/*.ts' 'libs/**/*.ts'", "format:ts": "prettier --write 'apps/**/*.ts' 'libs/**/*.ts'",
@ -53,6 +57,7 @@
"apps/*" "apps/*"
], ],
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.28.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.2", "@ianvs/prettier-plugin-sort-imports": "^4.4.2",
"@testcontainers/mongodb": "^10.7.2", "@testcontainers/mongodb": "^10.7.2",
"@testcontainers/postgresql": "^10.7.2", "@testcontainers/postgresql": "^10.7.2",
@ -60,7 +65,13 @@
"@types/node": "^22.15.30", "@types/node": "^22.15.30",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/yup": "^0.32.0", "@types/yup": "^0.32.0",
"@typescript-eslint/eslint-plugin": "^8.34.0",
"@typescript-eslint/parser": "^8.34.0",
"bun-types": "^1.2.15", "bun-types": "^1.2.15",
"eslint": "^9.28.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^7.2.1",
"mongodb-memory-server": "^9.1.6", "mongodb-memory-server": "^9.1.6",
"pg-mem": "^2.8.1", "pg-mem": "^2.8.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",

View file

@ -35,10 +35,7 @@
"@stock-bot/*": ["libs/*/src"] "@stock-bot/*": ["libs/*/src"]
} }
}, },
"exclude": [ "exclude": ["node_modules", "dist"],
"node_modules",
"dist"
],
"references": [ "references": [
// Core libraries first // Core libraries first
{ "path": "./libs/types" }, { "path": "./libs/types" },

View file

@ -22,7 +22,16 @@
"dependsOn": ["build"], "dependsOn": ["build"],
"outputs": [] "outputs": []
}, "lint": { }, "lint": {
"dependsOn": ["^lint"] "dependsOn": [],
"outputs": []
},
"lint:fix": {
"dependsOn": [],
"outputs": []
},
"lint:check": {
"dependsOn": [],
"outputs": []
}, },
"format": { "format": {
"dependsOn": [], "dependsOn": [],