fix: ticker-based asset_class fallback + backfill migration for NULL rows
- _normalize_asset_class() now accepts ticker param and infers class from a full ticker→class lookup table (energy/metals/agri/indices/equities/forex) - init_db() runs one-time UPDATE to backfill all NULL asset_class rows in trade_entry_prices and skipped_trades using known ticker lists - log_trade_entries and log_skipped_trade pass ticker to normalizer - Frontend _normalizeAssetClass() gets same ticker lookup + pattern fallbacks for =F futures, NSE: prefixed equities, =X currency pairs - All 3 filter calls now pass t.underlying as second argument Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -240,16 +240,61 @@ function _isBearishStr(strategy: string) {
|
||||
}
|
||||
|
||||
// Normalize AI-returned asset_class variants to canonical CATEGORIES keys
|
||||
function _normalizeAssetClass(cls: string | null | undefined): string {
|
||||
if (!cls) return ''
|
||||
const c = cls.toLowerCase().trim()
|
||||
if (/energy|oil|gas|petrol|brent|wti/.test(c)) return 'energy'
|
||||
if (/metal|gold|silver|copper|mining|precious/.test(c)) return 'metals'
|
||||
if (/agri|grain|corn|wheat|soy|crop|coton|coffee|cocoa/.test(c)) return 'agriculture'
|
||||
if (/index|indic|indices|spx|nasdaq|dow|s&p|russell|cac|dax/.test(c)) return 'indices'
|
||||
if (/equit|stock|action|share|sector|xle|xlf|xlk/.test(c)) return 'equities'
|
||||
if (/forex|currency|fx|devise|change|eur|usd|jpy|dxy/.test(c)) return 'forex'
|
||||
return c
|
||||
const _TICKER_TO_CLASS: Record<string, string> = {
|
||||
// Energy
|
||||
'CL=F': 'energy', 'BZ=F': 'energy', 'NG=F': 'energy', 'RB=F': 'energy', 'HO=F': 'energy',
|
||||
'XLE': 'energy', 'XOP': 'energy', 'USO': 'energy', 'UCO': 'energy', 'BOIL': 'energy', 'UNG': 'energy',
|
||||
// Metals
|
||||
'GC=F': 'metals', 'SI=F': 'metals', 'HG=F': 'metals', 'PA=F': 'metals', 'PL=F': 'metals',
|
||||
'GLD': 'metals', 'IAU': 'metals', 'SLV': 'metals', 'GDX': 'metals', 'GDXJ': 'metals',
|
||||
'PPLT': 'metals', 'DBP': 'metals', 'GLDM': 'metals',
|
||||
// Agriculture
|
||||
'ZC=F': 'agriculture', 'ZS=F': 'agriculture', 'ZW=F': 'agriculture',
|
||||
'CC=F': 'agriculture', 'KC=F': 'agriculture', 'CT=F': 'agriculture', 'OJ=F': 'agriculture',
|
||||
'CORN': 'agriculture', 'WEAT': 'agriculture', 'SOYB': 'agriculture', 'DBA': 'agriculture',
|
||||
// Indices
|
||||
'^GSPC': 'indices', '^NDX': 'indices', '^DJI': 'indices', '^RUT': 'indices',
|
||||
'SPY': 'indices', 'QQQ': 'indices', 'IWM': 'indices', 'DIA': 'indices', 'RSP': 'indices',
|
||||
'VGK': 'indices', 'EEM': 'indices', 'EWZ': 'indices', 'FXI': 'indices',
|
||||
'EWJ': 'indices', 'EFA': 'indices', 'ACWI': 'indices', 'INDA': 'indices',
|
||||
'^NSEI': 'indices', '^HSI': 'indices', 'EWG': 'indices', 'EWU': 'indices', 'EWI': 'indices',
|
||||
// Equities (sector ETFs)
|
||||
'XLF': 'equities', 'XLK': 'equities', 'XLV': 'equities', 'XLI': 'equities',
|
||||
'XLP': 'equities', 'XLU': 'equities', 'XLY': 'equities', 'XLRE': 'equities',
|
||||
'XLB': 'equities', 'XLC': 'equities',
|
||||
// Forex
|
||||
'DX-Y.NYB': 'forex', 'UUP': 'forex', 'FXE': 'forex', 'FXY': 'forex',
|
||||
'EUO': 'forex', 'YCS': 'forex', 'FXA': 'forex', 'FXB': 'forex', 'FXF': 'forex',
|
||||
}
|
||||
|
||||
function _normalizeAssetClass(cls: string | null | undefined, ticker?: string | null): string {
|
||||
if (cls) {
|
||||
const c = cls.toLowerCase().trim()
|
||||
if (/energy|oil|gas|petrol|brent|wti/.test(c)) return 'energy'
|
||||
if (/metal|gold|silver|copper|mining|precious/.test(c)) return 'metals'
|
||||
if (/agri|grain|corn|wheat|soy|crop|coton|coffee|cocoa/.test(c)) return 'agriculture'
|
||||
if (/index|indic|indices|spx|nasdaq|dow|s&p|russell|cac|dax/.test(c)) return 'indices'
|
||||
if (/equit|stock|action|share|sector/.test(c)) return 'equities'
|
||||
if (/forex|currency|fx|devise|change|eur|usd|jpy|dxy/.test(c)) return 'forex'
|
||||
if (['energy', 'metals', 'agriculture', 'indices', 'equities', 'forex'].includes(c)) return c
|
||||
}
|
||||
// Fallback: infer from ticker symbol
|
||||
if (ticker) {
|
||||
const t = ticker.toUpperCase().trim()
|
||||
if (_TICKER_TO_CLASS[t]) return _TICKER_TO_CLASS[t]
|
||||
// NSE: or other exchange-prefixed equities
|
||||
if (t.includes(':')) return 'equities'
|
||||
// Remaining =F futures: crude/energy or precious/metals heuristic
|
||||
if (t.endsWith('=F')) {
|
||||
const stem = t.slice(0, 2)
|
||||
if (['CL', 'RB', 'HO', 'NG', 'BZ'].includes(stem)) return 'energy'
|
||||
if (['GC', 'SI', 'HG', 'PA', 'PL'].includes(stem)) return 'metals'
|
||||
if (['ZC', 'ZS', 'ZW', 'CC', 'KC', 'CT'].includes(stem)) return 'agriculture'
|
||||
}
|
||||
// Currency pairs
|
||||
if (t.includes('=X') || t.includes('/')) return 'forex'
|
||||
}
|
||||
return cls?.toLowerCase().trim() ?? ''
|
||||
}
|
||||
|
||||
interface FilterBarProps {
|
||||
@@ -343,7 +388,7 @@ function ClosedTradesSection({ days }: { days: number }) {
|
||||
const trades = allTrades.filter((t: any) => {
|
||||
const q = search.toLowerCase()
|
||||
if (q && !`${t.underlying} ${t.strategy ?? ''} ${t.pattern_name ?? ''}`.toLowerCase().includes(q)) return false
|
||||
if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class) !== assetClass) return false
|
||||
if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class, t.underlying) !== assetClass) return false
|
||||
const bearish = _isBearishStr(t.strategy ?? '')
|
||||
if (direction === 'bullish' && bearish) return false
|
||||
if (direction === 'bearish' && !bearish) return false
|
||||
@@ -913,7 +958,7 @@ function TradeMtmSection({ days }: { days: number }) {
|
||||
if ((t.latest_score ?? t.score_at_entry ?? 0) < minScoreFilter) return false
|
||||
const q = search.toLowerCase()
|
||||
if (q && !`${t.underlying} ${t.strategy ?? ''} ${t.pattern_name ?? ''}`.toLowerCase().includes(q)) return false
|
||||
if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class) !== assetClass) return false
|
||||
if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class, t.underlying) !== assetClass) return false
|
||||
const bearish = _isBearishStr(t.strategy ?? '')
|
||||
if (direction === 'bullish' && bearish) return false
|
||||
if (direction === 'bearish' && !bearish) return false
|
||||
@@ -1645,7 +1690,7 @@ function SkippedTradesSection({ days }: { days: number }) {
|
||||
const trades = allTrades.filter((t: any) => {
|
||||
const q = search.toLowerCase()
|
||||
if (q && !`${t.underlying} ${t.strategy ?? ''} ${t.pattern_name ?? ''}`.toLowerCase().includes(q)) return false
|
||||
if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class) !== assetClass) return false
|
||||
if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class, t.underlying) !== assetClass) return false
|
||||
if (reasonFilter !== 'all' && t.skip_reason !== reasonFilter) return false
|
||||
return true
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user