feat: Add Phase 4 advanced metrics and components

- Add advanced metrics dashboard with trade analytics
- Add new trading components (EntryTypeAnalysis, MultiDayPositionTracker, NewsEventTracker, etc.)
- Add strategy mode selector and trend confirmation
- Add risk automation panel and slippage correlation analysis
- Add daily trading plan enhancements with modal components
- Add custom hooks (useApi, useLocalStorage, useAdvancedTradeMetrics)
- Add broker service integration and trading API
- Add test setup and vitest configuration
- Include parquet data files for live market data
- Add comprehensive documentation in docs/ folder
This commit is contained in:
Krikorios
2025-11-27 10:23:58 +02:00
parent b5e2b02cb8
commit 48e60d015f
2019 changed files with 39793 additions and 257 deletions
+215
View File
@@ -0,0 +1,215 @@
// INSTRUCTIONS: Replace handleBuy, handleSell, and handleReset in App.tsx with these versions
// Also add loadPortfolioFromBackend and call it in useEffect
import { executeTradeAPI, getPortfolioAPI, resetSimulationAPI, convertBackendPortfolio } from './services/tradingAPI'
// Add this state at the top of App component
const [isLoadingTrade, setIsLoadingTrade] = useState(false)
// Add this function to load portfolio on mount
const loadPortfolioFromBackend = useCallback(async () => {
try {
const backendPortfolio = await getPortfolioAPI()
const converted = convertBackendPortfolio(backendPortfolio, currentPrice)
setPortfolio(converted)
console.log('✅ Portfolio loaded from backend:', converted)
} catch (error) {
console.error('Failed to load portfolio from backend:', error)
// Fall back to default portfolio
setPortfolio(recalcPortfolio(createInitialPortfolio(), currentPrice))
}
}, [currentPrice])
// Add this useEffect to load on mount
useEffect(() => {
if (syncToBackend) {
loadPortfolioFromBackend()
}
}, []) // Only run once on mount
// REPLACE handleBuy with this version
const handleBuy = useCallback(async (quantity: number) => {
if (quantity <= 0 || Number.isNaN(quantity)) return
if (!syncToBackend) {
// Original in-memory logic (keep for backward compatibility)
setPortfolio((prev) => {
const cost = quantity * currentPrice
if (cost > prev.cash) {
alert('Insufficient cash for this order')
return prev
}
const existing = prev.position
const totalQuantity = existing ? existing.quantity + quantity : quantity
const avgPrice = existing ? ((existing.avgPrice * existing.quantity + currentPrice * quantity) / totalQuantity) : currentPrice
const trade: Trade = {
id: `${Date.now()}-${Math.floor(Math.random() * 1000)}`,
timestamp: Date.now(),
action: 'BUY',
quantity,
price: currentPrice,
total: Number(cost.toFixed(2)),
}
const updated: Portfolio = {
...prev,
cash: Number((prev.cash - cost).toFixed(2)),
position: {
symbol: TRADING_SYMBOL,
quantity: Number(totalQuantity.toFixed(4)),
avgPrice: Number(avgPrice.toFixed(2)),
currentPrice,
unrealizedPnl: 0,
unrealizedPnlPercent: 0,
},
trades: [trade, ...prev.trades].slice(0, 200),
}
return recalcPortfolio(updated, currentPrice)
})
return
}
// NEW: Backend-persisted logic
setIsLoadingTrade(true)
try {
const response = await executeTradeAPI({
action: 'BUY',
quantity,
price: currentPrice,
symbol: TRADING_SYMBOL
})
// Reload portfolio from backend to ensure sync
const backendPortfolio = await getPortfolioAPI()
const converted = convertBackendPortfolio(backendPortfolio, currentPrice)
setPortfolio(converted)
console.log('✅ BUY trade executed and synced:', response.trade)
} catch (error: any) {
console.error('❌ Trade execution failed:', error)
if (error.response?.data?.detail) {
alert(`Trade failed: ${error.response.data.detail}`)
} else {
alert('Trade execution failed. Please try again.')
}
} finally {
setIsLoadingTrade(false)
}
}, [currentPrice, syncToBackend])
// REPLACE handleSell with this version
const handleSell = useCallback(async (quantity: number, reason = 'Manual exit') => {
if (!syncToBackend) {
// Original in-memory logic (keep for backward compatibility)
setPortfolio((prev) => {
const position = prev.position
if (!position) {
alert('No open position to close')
return prev
}
const size = Math.min(quantity, position.quantity)
if (size <= 0) return prev
const proceeds = size * currentPrice
const pnl = (currentPrice - position.avgPrice) * size
const trade: Trade = {
id: `${Date.now()}-${Math.floor(Math.random() * 1000)}`,
timestamp: Date.now(),
action: 'SELL',
quantity: size,
price: currentPrice,
total: Number(proceeds.toFixed(2)),
pnl: Number(pnl.toFixed(2)),
}
const remainingQty = Number((position.quantity - size).toFixed(4))
const nextPosition = remainingQty > 0.0001
? { ...position, quantity: remainingQty, currentPrice }
: null
const updated: Portfolio = {
...prev,
cash: Number((prev.cash + proceeds).toFixed(2)),
position: nextPosition,
trades: [trade, ...prev.trades].slice(0, 200),
}
if (reason.startsWith('Auto')) {
console.info(reason)
}
return recalcPortfolio(updated, currentPrice)
})
if (!reason.startsWith('Manual')) {
setAiAnalysis(null)
}
return
}
// NEW: Backend-persisted logic
setIsLoadingTrade(true)
try {
// Check if we have a position
const currentPortfolio = await getPortfolioAPI()
if (!currentPortfolio.position) {
alert('No open position to close')
return
}
const size = Math.min(quantity, currentPortfolio.position.quantity)
const response = await executeTradeAPI({
action: 'SELL',
quantity: size,
price: currentPrice,
symbol: TRADING_SYMBOL,
notes: reason
})
// Reload portfolio from backend to ensure sync
const backendPortfolio = await getPortfolioAPI()
const converted = convertBackendPortfolio(backendPortfolio, currentPrice)
setPortfolio(converted)
console.log('✅ SELL trade executed and synced:', response.trade)
if (reason.startsWith('Auto')) {
console.info(reason)
}
} catch (error: any) {
console.error('❌ Trade execution failed:', error)
if (error.response?.data?.detail) {
alert(`Trade failed: ${error.response.data.detail}`)
} else {
alert('Trade execution failed. Please try again.')
}
} finally {
setIsLoadingTrade(false)
}
if (!reason.startsWith('Manual')) {
setAiAnalysis(null)
}
}, [currentPrice, syncToBackend])
// REPLACE handleReset with this version
const handleReset = useCallback(async () => {
if (!syncToBackend) {
// Original in-memory logic
setPortfolio(recalcPortfolio(createInitialPortfolio(), currentPrice))
setAiAnalysis(null)
return
}
// NEW: Backend-persisted logic
setIsLoadingTrade(true)
try {
const response = await resetSimulationAPI()
const converted = convertBackendPortfolio(response.portfolio, currentPrice)
setPortfolio(converted)
setAiAnalysis(null)
console.log('✅ Simulation reset and synced')
} catch (error) {
console.error('❌ Reset failed:', error)
alert('Failed to reset simulation. Please try again.')
} finally {
setIsLoadingTrade(false)
}
}, [currentPrice, syncToBackend])
// OPTIONAL: Add loading indicator in your Trade panel
// Show a spinner or disable buttons when isLoadingTrade is true