fixed a lot of lint and work on utils

This commit is contained in:
Boki 2025-06-19 21:07:37 -04:00
parent 4881a38c32
commit 42bc2966df
17 changed files with 183 additions and 93 deletions

View file

@ -162,7 +162,7 @@ export class SyncManager {
// Helper methods
private async resolveExchange(exchangeCode: string): Promise<string | null> {
if (!exchangeCode) return null;
if (!exchangeCode) {return null;}
// Simple mapping - expand this as needed
const exchangeMap: Record<string, string> = {

View file

@ -10,7 +10,7 @@ export function Layout() {
// Determine title from current route
const getTitle = () => {
const path = location.pathname.replace('/', '');
if (!path || path === 'dashboard') return 'Dashboard';
if (!path || path === 'dashboard') {return 'Dashboard';}
return path.charAt(0).toUpperCase() + path.slice(1);
};

View file

@ -1,5 +1,4 @@
import React from 'react';
import { XMarkIcon } from '@heroicons/react/24/outline';
interface DialogProps {
open: boolean;
@ -8,7 +7,7 @@ interface DialogProps {
}
export function Dialog({ open, onOpenChange, children }: DialogProps) {
if (!open) return null;
if (!open) {return null;}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">

View file

@ -278,9 +278,9 @@ export function PortfolioTable() {
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
if (value >= 1e12) return <span className="font-mono">${(value / 1e12).toFixed(2)}T</span>;
if (value >= 1e9) return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;
if (value >= 1e6) return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;
if (value >= 1e12) {return <span className="font-mono">${(value / 1e12).toFixed(2)}T</span>;}
if (value >= 1e9) {return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;}
if (value >= 1e6) {return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;}
return <span className="font-mono">${value.toLocaleString()}</span>;
},
},
@ -327,8 +327,8 @@ export function PortfolioTable() {
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
if (value >= 1e9) return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;
if (value >= 1e6) return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;
if (value >= 1e9) {return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;}
if (value >= 1e6) {return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;}
return <span className="font-mono">${value.toLocaleString()}</span>;
},
},
@ -339,8 +339,8 @@ export function PortfolioTable() {
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
if (value >= 1e9) return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;
if (value >= 1e6) return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;
if (value >= 1e9) {return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;}
if (value >= 1e6) {return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;}
return <span className="font-mono">${value.toLocaleString()}</span>;
},
},

View file

@ -200,7 +200,7 @@ export function AddProviderMappingDialog({
const exchange = unmappedExchanges.find(
ex => ex.provider_exchange_code === selectedProviderExchange
);
if (!exchange) return null;
if (!exchange) {return null;}
return (
<div className="space-y-1 text-xs">

View file

@ -27,7 +27,7 @@ export function AddSourceDialog({
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!source || !sourceCode || !id || !name || !code) return;
if (!source || !sourceCode || !id || !name || !code) {return;}
setLoading(true);
try {
@ -52,8 +52,8 @@ export function AddSourceDialog({
setName('');
setCode('');
setAliases('');
} catch (error) {
console.error('Error adding source:', error);
} catch (_error) {
console.error('Error adding source:', _error);
} finally {
setLoading(false);
}

View file

@ -35,7 +35,7 @@ export function useFormValidation<T>(
onSuccess?: () => void,
onError?: (error: unknown) => void
) => {
if (!validate()) return;
if (!validate()) {return;}
setIsSubmitting(true);
try {

View file

@ -19,7 +19,7 @@ export function formatPercentage(value: number): string {
}
export function getValueColor(value: number): string {
if (value > 0) return 'text-success';
if (value < 0) return 'text-danger';
if (value > 0) {return 'text-success';}
if (value < 0) {return 'text-danger';}
return 'text-text-secondary';
}

View file

@ -23,9 +23,9 @@ export function formatPercentage(value: number, decimals = 2): string {
* Format large numbers with K, M, B suffixes
*/
export function formatNumber(num: number): string {
if (num >= 1e9) return (num / 1e9).toFixed(1) + 'B';
if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M';
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K';
if (num >= 1e9) {return (num / 1e9).toFixed(1) + 'B';}
if (num >= 1e6) {return (num / 1e6).toFixed(1) + 'M';}
if (num >= 1e3) {return (num / 1e3).toFixed(1) + 'K';}
return num.toString();
}
@ -33,8 +33,8 @@ export function formatNumber(num: number): string {
* Get color class based on numeric value (profit/loss)
*/
export function getValueColor(value: number): string {
if (value > 0) return 'text-success';
if (value < 0) return 'text-danger';
if (value > 0) {return 'text-success';}
if (value < 0) {return 'text-danger';}
return 'text-text-secondary';
}
@ -42,6 +42,6 @@ export function getValueColor(value: number): string {
* Truncate text to specified length
*/
export function truncateText(text: string, length: number): string {
if (text.length <= length) return text;
if (text.length <= length) {return text;}
return text.slice(0, length) + '...';
}

View file

@ -110,16 +110,16 @@ export class EnvLoader implements ConfigLoader {
}
// Handle booleans
if (value.toLowerCase() === 'true') return true;
if (value.toLowerCase() === 'false') return false;
if (value.toLowerCase() === 'true') {return true;}
if (value.toLowerCase() === 'false') {return false;}
// Handle numbers
const num = Number(value);
if (!isNaN(num) && value !== '') return num;
if (!isNaN(num) && value !== '') {return num;}
// Handle null/undefined
if (value.toLowerCase() === 'null') return null;
if (value.toLowerCase() === 'undefined') return undefined;
if (value.toLowerCase() === 'null') {return null;}
if (value.toLowerCase() === 'undefined') {return undefined;}
// Return as string
return value;

View file

@ -93,7 +93,7 @@ export class QueueRateLimiter {
r.handler === handler &&
r.operation === operation
);
if (rule) return rule;
if (rule) {return rule;}
// 2. Check for handler-specific rule
rule = this.rules.find(r =>
@ -101,14 +101,14 @@ export class QueueRateLimiter {
r.queueName === queueName &&
r.handler === handler
);
if (rule) return rule;
if (rule) {return rule;}
// 3. Check for queue-specific rule
rule = this.rules.find(r =>
r.level === 'queue' &&
r.queueName === queueName
);
if (rule) return rule;
if (rule) {return rule;}
// 4. Check for global rule (least specific)
rule = this.rules.find(r => r.level === 'global');

View file

@ -210,7 +210,7 @@ describe('QueueRateLimiter', () => {
// Consume the limit
await rateLimiter.checkLimit('reset-test', 'operation');
let blocked = await rateLimiter.checkLimit('reset-test', 'operation');
const blocked = await rateLimiter.checkLimit('reset-test', 'operation');
expect(blocked.allowed).toBe(false);
// Reset limits
@ -248,7 +248,7 @@ describe('QueueRateLimiter', () => {
// Verify rule exists
await rateLimiter.checkLimit('remove-test', 'op');
let blocked = await rateLimiter.checkLimit('remove-test', 'op');
const blocked = await rateLimiter.checkLimit('remove-test', 'op');
expect(blocked.allowed).toBe(false);
// Remove rule

View file

@ -82,11 +82,11 @@ export {
massIndex,
coppockCurve
} from './technical-indicators';
// export * from './risk-metrics';
export * from './risk-metrics';
// export * from './portfolio-analytics';
// export * from './options-pricing';
// export * from './position-sizing';
// export * from './performance-metrics';
export * from './performance-metrics';
// export * from './market-statistics';
// export * from './volatility-models';
// export * from './correlation-analysis';

View file

@ -3,7 +3,22 @@
* Comprehensive performance measurement tools for trading strategies and portfolios
*/
import { PortfolioMetrics, ulcerIndex } from './index';
// import type { PortfolioMetrics } from '@stock-bot/types';
// Define PortfolioMetrics locally until it's added to types library
export interface PortfolioMetrics {
totalValue: number;
totalReturn: number;
totalReturnPercent: number;
dailyReturn: number;
dailyReturnPercent: number;
maxDrawdown: number;
sharpeRatio: number;
beta: number;
alpha: number;
volatility: number;
}
import { ulcerIndex } from './risk-metrics';
export interface TradePerformance {
totalTrades: number;
@ -140,8 +155,11 @@ export function analyzeDrawdowns(
};
}
let peak = equityCurve[0].value;
let peakDate = equityCurve[0].date;
const first = equityCurve[0];
if (!first) {return { maxDrawdown: 0, maxDrawdownDuration: 0, averageDrawdown: 0, drawdownPeriods: [] };}
let peak = first.value;
let peakDate = first.date;
let maxDrawdown = 0;
let maxDrawdownDuration = 0;
@ -157,19 +175,23 @@ export function analyzeDrawdowns(
for (let i = 1; i < equityCurve.length; i++) {
const current = equityCurve[i];
if (!current) {continue;}
if (current.value > peak) {
// New peak - end any current drawdown
if (currentDrawdownStart) {
const drawdownMagnitude = (peak - equityCurve[i - 1].value) / peak;
const prev = equityCurve[i - 1];
if (!prev) {continue;}
const drawdownMagnitude = (peak - prev.value) / peak;
const duration = Math.floor(
(equityCurve[i - 1].date.getTime() - currentDrawdownStart.getTime()) /
(prev.date.getTime() - currentDrawdownStart.getTime()) /
(1000 * 60 * 60 * 24)
);
drawdownPeriods.push({
start: currentDrawdownStart,
end: equityCurve[i - 1].date,
end: prev.date,
duration,
magnitude: drawdownMagnitude,
});
@ -195,6 +217,8 @@ export function analyzeDrawdowns(
// Handle ongoing drawdown
if (currentDrawdownStart) {
const lastPoint = equityCurve[equityCurve.length - 1];
if (!lastPoint) {return { maxDrawdown, maxDrawdownDuration, averageDrawdown: 0, drawdownPeriods };}
const drawdownMagnitude = (peak - lastPoint.value) / peak;
const duration = Math.floor(
(lastPoint.date.getTime() - currentDrawdownStart.getTime()) / (1000 * 60 * 60 * 24)
@ -352,8 +376,10 @@ export function strategyPerformanceAttribution(
for (let i = 0; i < sectorWeights.length; i++) {
const portfolioWeight = sectorWeights[i];
const benchmarkWeight = 1 / sectorWeights.length; // Assuming equal benchmark weights
const sectorReturn = sectorReturns[i];
if (portfolioWeight === undefined || sectorReturn === undefined) {continue;}
const benchmarkWeight = 1 / sectorWeights.length; // Assuming equal benchmark weights
// Allocation effect: (portfolio weight - benchmark weight) * (benchmark sector return - benchmark return)
allocationEffect += (portfolioWeight - benchmarkWeight) * (sectorReturn - benchmarkReturn);
@ -454,15 +480,23 @@ export function calculateStrategyMetrics(
const returns = [];
for (let i = 1; i < equityCurve.length; i++) {
const ret = (equityCurve[i].value - equityCurve[i - 1].value) / equityCurve[i - 1].value;
const current = equityCurve[i];
const previous = equityCurve[i - 1];
if (!current || !previous) {continue;}
const ret = (current.value - previous.value) / previous.value;
returns.push(ret);
}
const totalValue = equityCurve[equityCurve.length - 1].value;
const totalReturn = totalValue - equityCurve[0].value;
const totalReturnPercent = (totalReturn / equityCurve[0].value) * 100;
const lastPoint = equityCurve[equityCurve.length - 1];
const firstPoint = equityCurve[0];
if (!lastPoint || !firstPoint) {return { totalValue: 0, totalReturn: 0, totalReturnPercent: 0, dailyReturn: 0, dailyReturnPercent: 0, maxDrawdown: 0, sharpeRatio: 0, beta: 0, alpha: 0, volatility: 0 };}
const totalValue = lastPoint.value;
const totalReturn = totalValue - firstPoint.value;
const totalReturnPercent = (totalReturn / firstPoint.value) * 100;
const dailyReturn = returns[returns.length - 1];
const dailyReturn = returns[returns.length - 1] || 0;
const dailyReturnPercent = dailyReturn * 100;
const maxDrawdown = analyzeDrawdowns(equityCurve).maxDrawdown;
@ -528,7 +562,10 @@ export function informationRatio(portfolioReturns: number[], benchmarkReturns: n
}
const excessReturns = portfolioReturns.map(
(portfolioReturn, index) => portfolioReturn - benchmarkReturns[index]
(portfolioReturn, index) => {
const benchmark = benchmarkReturns[index];
return benchmark !== undefined ? portfolioReturn - benchmark : 0;
}
);
const trackingError = calculateVolatility(excessReturns);
const avgExcessReturn = excessReturns.reduce((sum, ret) => sum + ret, 0) / excessReturns.length;
@ -536,20 +573,7 @@ export function informationRatio(portfolioReturns: number[], benchmarkReturns: n
return trackingError === 0 ? 0 : avgExcessReturn / trackingError;
}
/**
* Calculate Treynor Ratio
*/
export function treynorRatio(
portfolioReturns: number[],
marketReturns: number[],
riskFreeRate: number
): number {
const beta = calculateBeta(portfolioReturns, marketReturns);
const avgPortfolioReturn =
portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
return beta === 0 ? 0 : (avgPortfolioReturn - riskFreeRate) / beta;
}
// Treynor Ratio is already exported from risk-metrics
/**
* Calculate Jensen's Alpha (same as Alpha, but included for clarity)
@ -575,11 +599,15 @@ export function captureRatio(
let downMarketPeriods = 0;
for (let i = 0; i < portfolioReturns.length; i++) {
if (benchmarkReturns[i] > 0) {
upCapture += portfolioReturns[i];
const benchmarkReturn = benchmarkReturns[i];
const portfolioReturn = portfolioReturns[i];
if (benchmarkReturn === undefined || portfolioReturn === undefined) {continue;}
if (benchmarkReturn > 0) {
upCapture += portfolioReturn;
upMarketPeriods++;
} else if (benchmarkReturns[i] < 0) {
downCapture += portfolioReturns[i];
} else if (benchmarkReturn < 0) {
downCapture += portfolioReturn;
downMarketPeriods++;
}
}
@ -701,11 +729,20 @@ export function calculateRollingAlpha(
export function timeWeightedRateOfReturn(
cashFlows: Array<{ amount: number; date: Date; value: number }>
): number {
if (cashFlows.length < 2) {
return 0;
}
const first = cashFlows[0];
if (!first) {return 0;}
let totalReturn = 1;
let previousValue = cashFlows[0].value;
let previousValue = first.value;
for (let i = 1; i < cashFlows.length; i++) {
const current = cashFlows[i];
if (!current) {continue;}
const periodReturn =
(current.value - previousValue - current.amount) / (previousValue + current.amount);
totalReturn *= 1 + periodReturn;
@ -721,13 +758,20 @@ export function timeWeightedRateOfReturn(
export function moneyWeightedRateOfReturn(
cashFlows: Array<{ amount: number; date: Date; value: number }>
): number {
if (cashFlows.length === 0) {
return 0;
}
const first = cashFlows[0];
if (!first) {return 0;}
// Approximate MWRR using Internal Rate of Return (IRR)
// This requires a numerical method or library for accurate IRR calculation
// This is a simplified example and may not be accurate for all cases
let totalCashFlow = 0;
let totalWeightedCashFlow = 0;
const startDate = cashFlows[0].date.getTime();
const startDate = first.date.getTime();
for (const cf of cashFlows) {
const timeDiff = (cf.date.getTime() - startDate) / (1000 * 60 * 60 * 24 * 365); // Years
@ -736,7 +780,7 @@ export function moneyWeightedRateOfReturn(
}
// Simplified approximation: MWRR ≈ totalCashFlow / totalWeightedCashFlow - 1
return totalCashFlow / totalWeightedCashFlow - 1;
return totalWeightedCashFlow === 0 ? 0 : totalCashFlow / totalWeightedCashFlow - 1;
}
// Helper functions
@ -779,8 +823,12 @@ function calculateBeta(portfolioReturns: number[], marketReturns: number[]): num
let marketVariance = 0;
for (let i = 0; i < portfolioReturns.length; i++) {
const portfolioDiff = portfolioReturns[i] - portfolioMean;
const marketDiff = marketReturns[i] - marketMean;
const portfolioReturn = portfolioReturns[i];
const marketReturn = marketReturns[i];
if (portfolioReturn === undefined || marketReturn === undefined) {continue;}
const portfolioDiff = portfolioReturn - portfolioMean;
const marketDiff = marketReturn - marketMean;
covariance += portfolioDiff * marketDiff;
marketVariance += marketDiff * marketDiff;

View file

@ -3,7 +3,7 @@
* Comprehensive risk measurement tools for portfolio and trading analysis
*/
import { RiskMetrics, treynorRatio } from './index';
import type { RiskMetrics } from '@stock-bot/types';
/**
* Calculate Value at Risk (VaR) using historical simulation
@ -31,7 +31,7 @@ export function conditionalValueAtRisk(returns: number[], confidenceLevel: numbe
const cutoffIndex = Math.floor((1 - confidenceLevel) * sortedReturns.length);
if (cutoffIndex === 0) {
return sortedReturns[0];
return sortedReturns[0] || 0;
}
const tailReturns = sortedReturns.slice(0, cutoffIndex);
@ -70,13 +70,19 @@ export function maxDrawdown(equityCurve: number[]): number {
}
let maxDD = 0;
let peak = equityCurve[0];
const first = equityCurve[0];
if (first === undefined) {return 0;}
let peak = first;
for (let i = 1; i < equityCurve.length; i++) {
if (equityCurve[i] > peak) {
peak = equityCurve[i];
const current = equityCurve[i];
if (current === undefined) {continue;}
if (current > peak) {
peak = current;
} else {
const drawdown = (peak - equityCurve[i]) / peak;
const drawdown = (peak - current) / peak;
maxDD = Math.max(maxDD, drawdown);
}
}
@ -142,8 +148,12 @@ export function beta(portfolioReturns: number[], marketReturns: number[]): numbe
let marketVariance = 0;
for (let i = 0; i < n; i++) {
const portfolioDiff = portfolioReturns[i] - portfolioMean;
const marketDiff = marketReturns[i] - marketMean;
const portfolioReturn = portfolioReturns[i];
const marketReturn = marketReturns[i];
if (portfolioReturn === undefined || marketReturn === undefined) {continue;}
const portfolioDiff = portfolioReturn - portfolioMean;
const marketDiff = marketReturn - marketMean;
covariance += portfolioDiff * marketDiff;
marketVariance += marketDiff * marketDiff;
@ -168,6 +178,24 @@ export function alpha(
return portfolioMean - (riskFreeRate + portfolioBeta * (marketMean - riskFreeRate));
}
/**
* Calculate Treynor ratio
*/
export function treynorRatio(
portfolioReturns: number[],
marketReturns: number[],
riskFreeRate: number = 0
): number {
const portfolioBeta = beta(portfolioReturns, marketReturns);
if (portfolioBeta === 0) {
return 0;
}
const portfolioMean = portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
return (portfolioMean - riskFreeRate) / portfolioBeta;
}
/**
* Calculate tracking error
*/
@ -176,7 +204,10 @@ export function trackingError(portfolioReturns: number[], benchmarkReturns: numb
return 0;
}
const activeReturns = portfolioReturns.map((ret, i) => ret - benchmarkReturns[i]);
const activeReturns = portfolioReturns.map((ret, i) => {
const benchmark = benchmarkReturns[i];
return benchmark !== undefined ? ret - benchmark : 0;
});
const mean = activeReturns.reduce((sum, ret) => sum + ret, 0) / activeReturns.length;
const variance =
@ -380,13 +411,22 @@ export function riskContribution(
for (let i = 0; i < n; i++) {
let marginalContribution = 0;
const row = covarianceMatrix[i];
if (!row) {continue;}
for (let j = 0; j < n; j++) {
marginalContribution += weights[j] * covarianceMatrix[i][j];
const weight = weights[j];
const covariance = row[j];
if (weight !== undefined && covariance !== undefined) {
marginalContribution += weight * covariance;
}
}
const contribution = (weights[i] * marginalContribution) / Math.pow(portfolioVolatility, 2);
contributions.push(contribution);
const weight = weights[i];
if (weight !== undefined && portfolioVolatility !== 0) {
const contribution = (weight * marginalContribution) / Math.pow(portfolioVolatility, 2);
contributions.push(contribution);
}
}
return contributions;
@ -396,8 +436,15 @@ export function riskContribution(
* Calculate Ulcer Index
*/
export function ulcerIndex(equityCurve: Array<{ value: number; date: Date }>): number {
if (equityCurve.length === 0) {
return 0;
}
let sumSquaredDrawdown = 0;
let peak = equityCurve[0].value;
const first = equityCurve[0];
if (!first) {return 0;}
let peak = first.value;
for (const point of equityCurve) {
peak = Math.max(peak, point.value);

View file

@ -540,7 +540,7 @@ export function adx(
for (let i = 1; i < ohlcv.length; i++) {
const current = ohlcv[i];
const previous = ohlcv[i - 1];
if (!current || !previous) continue;
if (!current || !previous) {continue;}
// True Range
const tr = Math.max(
@ -575,7 +575,7 @@ export function adx(
const atr = atrValues[i];
const plusDMSmoothed = smoothedPlusDM[i];
const minusDMSmoothed = smoothedMinusDM[i];
if (atr === undefined || plusDMSmoothed === undefined || minusDMSmoothed === undefined) continue;
if (atr === undefined || plusDMSmoothed === undefined || minusDMSmoothed === undefined) {continue;}
const diPlus = atr > 0 ? (plusDMSmoothed / atr) * 100 : 0;
const diMinus = atr > 0 ? (minusDMSmoothed / atr) * 100 : 0;
@ -612,7 +612,7 @@ export function parabolicSAR(
}
const first = ohlcv[0];
if (!first) return [];
if (!first) {return [];}
const result: number[] = [];
let trend = 1; // 1 for uptrend, -1 for downtrend
@ -625,7 +625,7 @@ export function parabolicSAR(
for (let i = 1; i < ohlcv.length; i++) {
const curr = ohlcv[i];
const prev = ohlcv[i - 1];
if (!curr || !prev) continue;
if (!curr || !prev) {continue;}
// Calculate new SAR
sar = sar + acceleration * (extremePoint - sar);

View file

@ -9,17 +9,13 @@ import {
dynamicPositionSize,
equalWeightPositionSize,
expectancyPositionSize,
fixedFractionalPositionSize,
fixedRiskPositionSize,
fractionalKellyPositionSize,
kellyPositionSize,
liquidityConstrainedPositionSize,
monteCarloPositionSize,
multiTimeframePositionSize,
riskParityPositionSize,
sharpeOptimizedPositionSize,
validatePositionSize,
volatilityAdjustedPositionSize,
volatilityTargetPositionSize,
type KellyParams,
type PositionSizeParams,