borsa / nextjs-app /src /lib /advanced-ml.ts
veteroner's picture
feat: live position monitoring with charts + trading system production ready
656ac31
/**
* Advanced ML Features - Python predictions.py eşdeğeri
* Gelişmiş makine öğrenimi özellikleri
*/
interface AdvancedFeatures {
// Lag features (gecikmeli değerler)
close_lag_1?: number
close_lag_2?: number
close_lag_3?: number
close_lag_5?: number
close_lag_10?: number
// Price changes (fiyat değişimleri)
price_change_1d?: number
price_change_3d?: number
price_change_5d?: number
price_change_10d?: number
price_change_20d?: number
// Moving average ratios
sma5_sma20_ratio?: number
price_sma20_ratio?: number
price_sma50_ratio?: number
// Volatility indicators
volatility_5d?: number
volatility_10d?: number
volatility_20d?: number
price_range_5d?: number
price_range_10d?: number
// Momentum indicators
momentum_5d?: number
momentum_10d?: number
momentum_20d?: number
// Volume indicators
volume_change_1d?: number
volume_change_5d?: number
volume_ma_5?: number
volume_ma_10?: number
volume_ma_ratio_5?: number
// Bollinger position
bb_position?: number
// MACD histogram change
macd_hist_change?: number
}
interface PriceData {
date: string
open: number
high: number
low: number
close: number
volume: number
}
interface DataQuality {
score: number // 0-1
length_factor: number
nan_factor: number
volume_factor: number
ohlc_factor: number
}
interface ConfidenceFactors {
r2_component: number
direction_component: number
mape_component: number
data_quality_component: number
volatility_component: number
model_bonus: number
}
/**
* Create advanced features from historical price data
*/
export function createAdvancedFeatures(
data: PriceData[],
indicators?: Record<string, unknown>
): AdvancedFeatures {
const features: AdvancedFeatures = {}
if (data.length < 30) return features
const closes = data.map(d => d.close)
const volumes = data.map(d => d.volume)
const highs = data.map(d => d.high)
const lows = data.map(d => d.low)
const lastIdx = data.length - 1
// Lag features
if (lastIdx >= 1) features.close_lag_1 = closes[lastIdx - 1]
if (lastIdx >= 2) features.close_lag_2 = closes[lastIdx - 2]
if (lastIdx >= 3) features.close_lag_3 = closes[lastIdx - 3]
if (lastIdx >= 5) features.close_lag_5 = closes[lastIdx - 5]
if (lastIdx >= 10) features.close_lag_10 = closes[lastIdx - 10]
// Price changes
if (lastIdx >= 1) {
features.price_change_1d = (closes[lastIdx] - closes[lastIdx - 1]) / closes[lastIdx - 1]
}
if (lastIdx >= 3) {
features.price_change_3d = (closes[lastIdx] - closes[lastIdx - 3]) / closes[lastIdx - 3]
}
if (lastIdx >= 5) {
features.price_change_5d = (closes[lastIdx] - closes[lastIdx - 5]) / closes[lastIdx - 5]
}
if (lastIdx >= 10) {
features.price_change_10d = (closes[lastIdx] - closes[lastIdx - 10]) / closes[lastIdx - 10]
}
if (lastIdx >= 20) {
features.price_change_20d = (closes[lastIdx] - closes[lastIdx - 20]) / closes[lastIdx - 20]
}
// Moving averages
const sma5 = calculateSMA(closes, 5)
const sma20 = calculateSMA(closes, 20)
const sma50 = calculateSMA(closes, 50)
if (sma5 && sma20) {
features.sma5_sma20_ratio = sma5 / sma20
}
if (sma20) {
features.price_sma20_ratio = closes[lastIdx] / sma20
}
if (sma50) {
features.price_sma50_ratio = closes[lastIdx] / sma50
}
// Volatility
features.volatility_5d = calculateVolatility(closes, 5)
features.volatility_10d = calculateVolatility(closes, 10)
features.volatility_20d = calculateVolatility(closes, 20)
features.price_range_5d = calculatePriceRange(highs, lows, closes, 5)
features.price_range_10d = calculatePriceRange(highs, lows, closes, 10)
// Momentum
if (lastIdx >= 5) {
features.momentum_5d = closes[lastIdx] / closes[lastIdx - 5] - 1
}
if (lastIdx >= 10) {
features.momentum_10d = closes[lastIdx] / closes[lastIdx - 10] - 1
}
if (lastIdx >= 20) {
features.momentum_20d = closes[lastIdx] / closes[lastIdx - 20] - 1
}
// Volume
if (volumes.length >= 2) {
features.volume_change_1d = (volumes[lastIdx] - volumes[lastIdx - 1]) / volumes[lastIdx - 1]
}
if (volumes.length >= 5) {
features.volume_change_5d = (volumes[lastIdx] - volumes[lastIdx - 5]) / volumes[lastIdx - 5]
}
features.volume_ma_5 = calculateSMA(volumes, 5)
features.volume_ma_10 = calculateSMA(volumes, 10)
if (features.volume_ma_5) {
features.volume_ma_ratio_5 = volumes[lastIdx] / features.volume_ma_5
}
// Bollinger Band position (from indicators if available)
if (typeof indicators?.bb_upper === 'number' && typeof indicators?.bb_lower === 'number') {
const bbRange = indicators.bb_upper - indicators.bb_lower
if (bbRange > 0) {
features.bb_position = (closes[lastIdx] - indicators.bb_lower) / bbRange
}
}
// MACD histogram change (from indicators if available)
if (typeof indicators?.macd_hist === 'number' && typeof indicators?.macd_hist_prev === 'number') {
features.macd_hist_change = indicators.macd_hist - indicators.macd_hist_prev
}
return features
}
/**
* Calculate Simple Moving Average
*/
function calculateSMA(data: number[], period: number): number | undefined {
if (data.length < period) return undefined
const slice = data.slice(-period)
const sum = slice.reduce((a, b) => a + b, 0)
return sum / period
}
/**
* Calculate Volatility (standard deviation)
*/
function calculateVolatility(prices: number[], period: number): number | undefined {
if (prices.length < period) return undefined
const slice = prices.slice(-period)
const mean = slice.reduce((a, b) => a + b, 0) / period
const variance = slice.reduce((sum, price) => sum + Math.pow(price - mean, 2), 0) / period
return Math.sqrt(variance)
}
/**
* Calculate Price Range
*/
function calculatePriceRange(
highs: number[],
lows: number[],
closes: number[],
period: number
): number | undefined {
if (highs.length < period) return undefined
const highSlice = highs.slice(-period)
const lowSlice = lows.slice(-period)
const currentClose = closes[closes.length - 1]
const maxHigh = Math.max(...highSlice)
const minLow = Math.min(...lowSlice)
return (maxHigh - minLow) / currentClose
}
/**
* Calculate data quality score (0-1)
*/
export function calculateDataQualityScore(data: PriceData[]): DataQuality {
// Length factor (ideal: 1 year = 252 days)
const length_factor = Math.min(1.0, data.length / 252)
// Volume factor (check for zero volumes)
const zeroVolumeCount = data.filter(d => d.volume === 0).length
const zeroVolumeRatio = zeroVolumeCount / data.length
const volume_factor = 1.0 - Math.min(0.3, zeroVolumeRatio)
// OHLC validity factor
const invalidOHLC = data.filter(d =>
d.high < d.low ||
d.high < d.open ||
d.high < d.close ||
d.low > d.open ||
d.low > d.close
).length
const invalidRatio = invalidOHLC / data.length
const ohlc_factor = 1.0 - Math.min(0.2, invalidRatio)
// NaN factor (assumed all valid in this case)
const nan_factor = 1.0
// Weighted quality score
const score = (
length_factor * 0.4 +
nan_factor * 0.3 +
volume_factor * 0.2 +
ohlc_factor * 0.1
)
return {
score: Math.max(0, Math.min(1, score)),
length_factor,
nan_factor,
volume_factor,
ohlc_factor
}
}
/**
* Enhanced confidence score calculation
*/
export function calculateEnhancedConfidence(params: {
prediction_accuracy?: number // 0-1
direction_accuracy?: number // 0-1
mape?: number // Mean Absolute Percentage Error
data_quality_score: number // 0-1
volatility: number // Annual volatility %
model_type: string
advanced_features_count?: number
}): { confidence: number; factors: ConfidenceFactors } {
const {
prediction_accuracy = 0.5,
direction_accuracy = 0.5,
mape = 10,
data_quality_score,
volatility,
model_type,
advanced_features_count = 0
} = params
// R2/Accuracy component (30%)
const r2_component = Math.max(0, Math.min(100, prediction_accuracy * 100))
// Direction accuracy component (25%)
const direction_component = direction_accuracy * 100
// MAPE component (20%) - lower is better
const mape_component = Math.max(0, 100 - mape * 2)
// Data quality component (15%)
const data_quality_component = data_quality_score * 100
// Volatility component (10%) - higher volatility reduces confidence
const volatility_component = Math.max(0, 100 - volatility * 5)
// Model bonus
let model_bonus = 0
if (model_type === 'Ensemble') {
model_bonus = 5
} else if (model_type === 'Hybrid') {
model_bonus = 3
} else if (model_type === 'XGBoost' || model_type === 'LightGBM') {
model_bonus = 2
} else if (model_type === 'RandomForest') {
model_bonus = 1
}
// Advanced features bonus (up to 3 points)
const features_bonus = Math.min(3, advanced_features_count / 10)
// Weighted total
const confidence = (
r2_component * 0.30 +
direction_component * 0.25 +
mape_component * 0.20 +
data_quality_component * 0.15 +
volatility_component * 0.10 +
model_bonus +
features_bonus
)
// Clamp between 30-95
const final_confidence = Math.max(30, Math.min(95, confidence))
return {
confidence: final_confidence,
factors: {
r2_component,
direction_component,
mape_component,
data_quality_component,
volatility_component,
model_bonus: model_bonus + features_bonus
}
}
}
/**
* Detect chart patterns
*/
export interface ChartPattern {
type: string
description: string
signal: 'bullish' | 'bearish' | 'neutral'
strength: number
start_idx?: number
end_idx?: number
}
export function detectChartPatterns(data: PriceData[]): ChartPattern[] {
const patterns: ChartPattern[] = []
if (data.length < 40) return patterns
const closes = data.map(d => d.close)
const window = Math.min(40, closes.length)
const lastPrices = closes.slice(-window)
// Double bottom detection
const localMins: Array<{idx: number, val: number}> = []
for (let i = 1; i < lastPrices.length - 1; i++) {
if (lastPrices[i] < lastPrices[i - 1] && lastPrices[i] < lastPrices[i + 1]) {
localMins.push({ idx: i, val: lastPrices[i] })
}
}
if (localMins.length >= 2) {
const lastTwoMins = localMins.slice(-2)
const [min1, min2] = lastTwoMins
const priceDiffPct = Math.abs(min1.val - min2.val) / min1.val * 100
const dayDiff = min2.idx - min1.idx
if (priceDiffPct < 5 && dayDiff >= 5) {
patterns.push({
type: 'double_bottom',
description: 'Çift Dip Formasyonu',
signal: 'bullish',
strength: 0.7,
start_idx: min1.idx,
end_idx: min2.idx
})
}
}
// Head and shoulders detection (simplified)
if (localMins.length >= 3) {
const peaks: Array<{idx: number, val: number}> = []
for (let i = 1; i < lastPrices.length - 1; i++) {
if (lastPrices[i] > lastPrices[i - 1] && lastPrices[i] > lastPrices[i + 1]) {
peaks.push({ idx: i, val: lastPrices[i] })
}
}
if (peaks.length >= 3) {
const lastThreePeaks = peaks.slice(-3)
const [left, head, right] = lastThreePeaks
// Head should be higher than shoulders
if (head.val > left.val && head.val > right.val) {
const shouldersDiff = Math.abs(left.val - right.val) / left.val * 100
if (shouldersDiff < 5) {
patterns.push({
type: 'head_shoulders',
description: 'Omuz Baş Omuz',
signal: 'bearish',
strength: 0.75
})
}
}
}
}
// Trend detection (ascending/descending triangle)
const recentPrices = closes.slice(-20)
if (recentPrices.length >= 20) {
const firstHalf = recentPrices.slice(0, 10)
const secondHalf = recentPrices.slice(10)
const firstAvg = firstHalf.reduce((a, b) => a + b, 0) / firstHalf.length
const secondAvg = secondHalf.reduce((a, b) => a + b, 0) / secondHalf.length
const trend = (secondAvg - firstAvg) / firstAvg
if (trend > 0.05) {
patterns.push({
type: 'ascending_trend',
description: 'Yükselen Trend',
signal: 'bullish',
strength: Math.min(0.9, trend * 10)
})
} else if (trend < -0.05) {
patterns.push({
type: 'descending_trend',
description: 'Düşen Trend',
signal: 'bearish',
strength: Math.min(0.9, Math.abs(trend) * 10)
})
}
}
return patterns
}
/**
* Calculate comprehensive ML score with all factors
*/
export function calculateComprehensiveMLScore(params: {
trend_score: number
momentum_score: number
volatility_score: number
pattern_score: number
advanced_features: AdvancedFeatures
chart_patterns: ChartPattern[]
data_quality: DataQuality
}): {
total_score: number
components: {
trend: number
momentum: number
volatility: number
pattern: number
features: number
quality: number
}
recommendation: 'BUY' | 'SELL' | 'HOLD'
strength: number
} {
const {
trend_score,
momentum_score,
volatility_score,
pattern_score,
advanced_features,
chart_patterns,
data_quality
} = params
// Advanced features score
const features_count = Object.keys(advanced_features).length
const features_score = Math.min(100, features_count * 2)
// Chart patterns score
const bullish_patterns = chart_patterns.filter(p => p.signal === 'bullish')
const bearish_patterns = chart_patterns.filter(p => p.signal === 'bearish')
const patterns_impact = (
bullish_patterns.reduce((sum, p) => sum + p.strength, 0) -
bearish_patterns.reduce((sum, p) => sum + p.strength, 0)
) * 10
// Quality adjustment
const quality_multiplier = 0.5 + (data_quality.score * 0.5)
// Weighted components
const components = {
trend: trend_score * 0.30,
momentum: momentum_score * 0.25,
volatility: volatility_score * 0.15,
pattern: pattern_score * 0.10,
features: features_score * 0.10,
quality: patterns_impact * 0.10
}
const raw_score = Object.values(components).reduce((a, b) => a + b, 0)
const total_score = raw_score * quality_multiplier
// Recommendation
let recommendation: 'BUY' | 'SELL' | 'HOLD'
let strength: number
if (total_score > 60) {
recommendation = 'BUY'
strength = Math.min(100, total_score)
} else if (total_score < 40) {
recommendation = 'SELL'
strength = Math.min(100, 100 - total_score)
} else {
recommendation = 'HOLD'
strength = 50
}
return {
total_score,
components,
recommendation,
strength
}
}