fixed a lot of lint and work on utils
This commit is contained in:
parent
4881a38c32
commit
42bc2966df
17 changed files with 183 additions and 93 deletions
|
|
@ -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> = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export function useFormValidation<T>(
|
|||
onSuccess?: () => void,
|
||||
onError?: (error: unknown) => void
|
||||
) => {
|
||||
if (!validate()) return;
|
||||
if (!validate()) {return;}
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) + '...';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -9,17 +9,13 @@ import {
|
|||
dynamicPositionSize,
|
||||
equalWeightPositionSize,
|
||||
expectancyPositionSize,
|
||||
fixedFractionalPositionSize,
|
||||
fixedRiskPositionSize,
|
||||
fractionalKellyPositionSize,
|
||||
kellyPositionSize,
|
||||
liquidityConstrainedPositionSize,
|
||||
monteCarloPositionSize,
|
||||
multiTimeframePositionSize,
|
||||
riskParityPositionSize,
|
||||
sharpeOptimizedPositionSize,
|
||||
validatePositionSize,
|
||||
volatilityAdjustedPositionSize,
|
||||
volatilityTargetPositionSize,
|
||||
type KellyParams,
|
||||
type PositionSizeParams,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue