linting
This commit is contained in:
parent
597c6efc9b
commit
8b5e06954a
26 changed files with 532 additions and 186 deletions
|
|
@ -1 +0,0 @@
|
||||||
// This file is deprecated in ESLint v9. Use eslint.config.js instead.
|
|
||||||
|
|
@ -82,11 +82,17 @@ 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) {
|
||||||
if (minutes < 60) {return `${minutes}m ago`;}
|
return 'Just now';
|
||||||
|
}
|
||||||
|
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`;
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,12 @@ export class PortfolioComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
getPnLColor(value: number): string {
|
getPnLColor(value: number): string {
|
||||||
if (value > 0) {return 'text-green-600';}
|
if (value > 0) {
|
||||||
if (value < 0) {return 'text-red-600';}
|
return 'text-green-600';
|
||||||
|
}
|
||||||
|
if (value < 0) {
|
||||||
|
return 'text-red-600';
|
||||||
|
}
|
||||||
return 'text-gray-600';
|
return 'text-gray-600';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ 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) {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ 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) {
|
||||||
|
|
|
||||||
|
|
@ -278,27 +278,45 @@ export class PerformanceMetricsComponent {
|
||||||
|
|
||||||
// Conditional classes
|
// Conditional classes
|
||||||
getReturnClass(value: number): string {
|
getReturnClass(value: number): string {
|
||||||
if (value > 0) {return 'positive';}
|
if (value > 0) {
|
||||||
if (value < 0) {return 'negative';}
|
return 'positive';
|
||||||
|
}
|
||||||
|
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) {
|
||||||
if (value >= 1) {return 'neutral';}
|
return 'positive';
|
||||||
if (value < 0) {return 'negative';}
|
}
|
||||||
|
if (value >= 1) {
|
||||||
|
return 'neutral';
|
||||||
|
}
|
||||||
|
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) {
|
||||||
if (value >= 0.45) {return 'neutral';}
|
return 'positive';
|
||||||
|
}
|
||||||
|
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) {
|
||||||
if (value >= 1) {return 'neutral';}
|
return 'positive';
|
||||||
|
}
|
||||||
|
if (value >= 1) {
|
||||||
|
return 'neutral';
|
||||||
|
}
|
||||||
return 'negative';
|
return 'negative';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,12 @@ 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') {
|
||||||
if (typeof source.total !== 'number') {source.total = 0;}
|
source.working = 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 +404,9 @@ 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(':');
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@
|
||||||
"@stock-bot/shutdown#build"
|
"@stock-bot/shutdown#build"
|
||||||
],
|
],
|
||||||
"outputs": ["dist/**"],
|
"outputs": ["dist/**"],
|
||||||
"inputs": ["src/**", "package.json", "tsconfig.json", "!**/*.test.ts", "!**/*.spec.ts", "!**/test/**", "!**/tests/**", "!**/__tests__/**"]
|
"inputs": ["src/**",
|
||||||
|
"package.json",
|
||||||
|
"tsconfig.json", "!**/*.test.ts", "!**/*.spec.ts", "!**/test/**", "!**/tests/**", "!**/__tests__/**"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,9 @@ 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 +85,9 @@ 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 +96,18 @@ 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 +117,25 @@ 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 +153,9 @@ 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 +173,9 @@ 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 +184,22 @@ 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 +211,9 @@ 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 +222,17 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,9 @@ 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 =>
|
||||||
|
|
|
||||||
4
libs/cache/src/redis-cache.ts
vendored
4
libs/cache/src/redis-cache.ts
vendored
|
|
@ -87,7 +87,9 @@ 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++;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@ 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 +48,9 @@ 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 +228,9 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,9 @@ 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,
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,9 @@ 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);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
* 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 +17,9 @@ 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 +27,9 @@ 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 +37,9 @@ 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 +99,12 @@ 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) {
|
||||||
if (Math.abs(dnpv) < 1e-10) {break;}
|
break;
|
||||||
|
}
|
||||||
|
if (Math.abs(dnpv) < 1e-10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
rate = rate - npv / dnpv;
|
rate = rate - npv / dnpv;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +198,9 @@ 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 +213,9 @@ 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 +374,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -918,7 +918,9 @@ 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 +965,12 @@ 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) {
|
||||||
if (x === 1) {return 1;}
|
return 0;
|
||||||
|
}
|
||||||
|
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 +990,9 @@ 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 +1000,32 @@ 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 +1073,15 @@ 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 +1237,12 @@ 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) {
|
||||||
if (f === Infinity) {return 1;}
|
return 0;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,9 @@ 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 +78,9 @@ 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 +231,13 @@ 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) {
|
||||||
else if (volatility < 0.03) {volatilityLevel = 'medium';}
|
volatilityLevel = 'low';
|
||||||
else {volatilityLevel = 'high';}
|
} else if (volatility < 0.03) {
|
||||||
|
volatilityLevel = 'medium';
|
||||||
|
} else {
|
||||||
|
volatilityLevel = 'high';
|
||||||
|
}
|
||||||
|
|
||||||
// Determine regime
|
// Determine regime
|
||||||
let regime: 'trending' | 'ranging' | 'volatile' | 'quiet';
|
let regime: 'trending' | 'ranging' | 'volatile' | 'quiet';
|
||||||
|
|
@ -281,7 +289,9 @@ 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 +462,15 @@ 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) {
|
||||||
else if (overallStress < 0.5) {stressLevel = 'medium';}
|
stressLevel = 'low';
|
||||||
else if (overallStress < 0.75) {stressLevel = 'high';}
|
} else if (overallStress < 0.5) {
|
||||||
else {stressLevel = 'extreme';}
|
stressLevel = 'medium';
|
||||||
|
} else if (overallStress < 0.75) {
|
||||||
|
stressLevel = 'high';
|
||||||
|
} else {
|
||||||
|
stressLevel = 'extreme';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stressLevel,
|
stressLevel,
|
||||||
|
|
@ -474,7 +489,9 @@ 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 +558,9 @@ 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 +585,9 @@ 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 +615,9 @@ 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 +646,9 @@ 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 +672,15 @@ 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 +699,9 @@ 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));
|
||||||
|
|
@ -813,8 +844,9 @@ export function garmanKlassVolatility(
|
||||||
openPrices.length !== lowPrices.length ||
|
openPrices.length !== lowPrices.length ||
|
||||||
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;
|
||||||
|
|
@ -849,8 +881,9 @@ export function yangZhangVolatility(
|
||||||
openPrices.length !== closePrices.length ||
|
openPrices.length !== closePrices.length ||
|
||||||
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 +910,9 @@ 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 +925,9 @@ 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 +942,9 @@ 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 +959,16 @@ 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 +978,9 @@ 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 +1005,18 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,9 @@ 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 +379,9 @@ 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 +397,9 @@ 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 +411,16 @@ 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 +620,9 @@ 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);
|
||||||
|
|
@ -630,8 +642,9 @@ export function calculateRollingBeta(
|
||||||
marketReturns: number[],
|
marketReturns: number[],
|
||||||
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[] = [];
|
||||||
|
|
||||||
|
|
@ -667,8 +680,9 @@ export function calculateRollingAlpha(
|
||||||
riskFreeRate: number,
|
riskFreeRate: number,
|
||||||
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 +742,9 @@ 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 +755,9 @@ 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 +767,9 @@ 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 +806,17 @@ 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 +825,17 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,9 @@ 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 +404,9 @@ 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 +460,9 @@ 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 +523,9 @@ 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 +567,9 @@ 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);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,12 @@ 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) {
|
||||||
if (entryPrice === stopLoss) {return 0;}
|
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 +52,9 @@ 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 +78,9 @@ 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 +96,9 @@ 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 +115,9 @@ 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 +133,9 @@ 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;
|
||||||
|
|
@ -142,12 +156,15 @@ export function expectancyPositionSize(
|
||||||
maxRiskPercentage: number = 2
|
maxRiskPercentage: number = 2
|
||||||
): 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 +184,9 @@ 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;
|
||||||
|
|
@ -229,8 +248,9 @@ export function sharpeOptimizedPositionSize(
|
||||||
maxLeverage: number = 3
|
maxLeverage: number = 3
|
||||||
): 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 +271,9 @@ 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 +291,9 @@ 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 +310,9 @@ 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 +336,9 @@ 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 +359,12 @@ 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) {
|
||||||
if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) {return basePositionSize;}
|
return 0;
|
||||||
|
}
|
||||||
|
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 +386,9 @@ 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 +406,9 @@ 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 +432,26 @@ 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 +512,9 @@ 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 +558,9 @@ 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 +571,9 @@ 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%
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ 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 +23,16 @@ 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 +46,9 @@ 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 +65,9 @@ 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 +88,15 @@ 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 +110,18 @@ 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 +190,9 @@ 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 +212,17 @@ 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 +234,17 @@ 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 +345,18 @@ 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 +416,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ 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 +27,9 @@ 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 +50,9 @@ 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 +147,9 @@ 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 +174,9 @@ 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 +203,9 @@ 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 +230,9 @@ 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 +258,9 @@ 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 +276,9 @@ 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 +298,9 @@ 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 +337,9 @@ 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 +363,9 @@ 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 +391,9 @@ 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 +432,9 @@ 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 +495,9 @@ 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 +535,9 @@ 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 +604,9 @@ 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 +641,9 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,9 @@ 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 +539,9 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -326,7 +326,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue