diff --git a/backend/app/api/indicators.py b/backend/app/api/indicators.py new file mode 100644 index 0000000..2b4b32c --- /dev/null +++ b/backend/app/api/indicators.py @@ -0,0 +1,522 @@ +""" +Phase 4: Advanced Indicators Management +Technical analysis indicators configuration and management +""" + +from fastapi import APIRouter, Query, HTTPException +from typing import List, Optional +from datetime import datetime + +router = APIRouter(prefix="/api/indicators", tags=["Technical Indicators"]) + + +# Available indicators with their parameters +AVAILABLE_INDICATORS = { + "moving_averages": { + "name": "Moving Averages", + "description": "SMA, EMA, DEMA, TEMA, WMA", + "indicators": [ + { + "id": "sma", + "name": "Simple Moving Average", + "periods": [5, 10, 20, 50, 100, 200], + "default_period": 20, + "type": "trend", + }, + { + "id": "ema", + "name": "Exponential Moving Average", + "periods": [5, 10, 20, 50, 100, 200], + "default_period": 12, + "type": "trend", + }, + { + "id": "wma", + "name": "Weighted Moving Average", + "periods": [5, 10, 20, 50], + "default_period": 20, + "type": "trend", + }, + ], + }, + "oscillators": { + "name": "Oscillators", + "description": "RSI, Stochastic, MACD, KDJ", + "indicators": [ + { + "id": "rsi", + "name": "Relative Strength Index", + "periods": [14], + "default_period": 14, + "bounds": [0, 100], + "overbought": 70, + "oversold": 30, + "type": "momentum", + }, + { + "id": "stochastic", + "name": "Stochastic Oscillator", + "periods": [14], + "smoothing": [3, 5, 7], + "default_period": 14, + "bounds": [0, 100], + "overbought": 80, + "oversold": 20, + "type": "momentum", + }, + { + "id": "macd", + "name": "MACD", + "fast_period": 12, + "slow_period": 26, + "signal_period": 9, + "type": "momentum", + }, + { + "id": "kdj", + "name": "KDJ Index", + "periods": [9, 14], + "default_period": 9, + "bounds": [0, 100], + "type": "momentum", + }, + ], + }, + "volatility": { + "name": "Volatility Indicators", + "description": "Bollinger Bands, ATR, Keltner Channel", + "indicators": [ + { + "id": "bb", + "name": "Bollinger Bands", + "periods": [20], + "default_period": 20, + "std_dev": 2, + "type": "volatility", + }, + { + "id": "atr", + "name": "Average True Range", + "periods": [14], + "default_period": 14, + "type": "volatility", + }, + { + "id": "kc", + "name": "Keltner Channel", + "periods": [20], + "default_period": 20, + "atr_mult": 2, + "type": "volatility", + }, + ], + }, + "support_resistance": { + "name": "Support & Resistance", + "description": "Pivot Points, Fibonacci, Trend Lines", + "indicators": [ + { + "id": "pivot", + "name": "Pivot Points", + "types": ["Classic", "Camarilla", "Woodie"], + "default_type": "Classic", + "type": "level", + }, + { + "id": "fibonacci", + "name": "Fibonacci Retracement", + "levels": [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0], + "type": "level", + }, + ], + }, + "volume": { + "name": "Volume Indicators", + "description": "OBV, Volume Profile, CMF", + "indicators": [ + { + "id": "obv", + "name": "On-Balance Volume", + "periods": [20], + "default_period": 20, + "type": "volume", + }, + { + "id": "cmf", + "name": "Chaikin Money Flow", + "periods": [20], + "default_period": 20, + "type": "volume", + }, + ], + }, +} + +# Default indicator configuration for gold trading +DEFAULT_INDICATORS = { + "trend": ["ema_12", "ema_26"], + "momentum": ["rsi_14", "macd"], + "volatility": ["bb_20", "atr_14"], + "support_resistance": ["pivot_classic"], +} + +# Mock user configurations +USER_INDICATORS = {} + + +@router.get("/available") +async def get_available_indicators(): + """Get all available technical indicators""" + return { + "total_categories": len(AVAILABLE_INDICATORS), + "categories": AVAILABLE_INDICATORS, + "total_indicators": sum( + len(cat.get("indicators", [])) for cat in AVAILABLE_INDICATORS.values() + ), + } + + +@router.get("/categories") +async def get_indicator_categories(): + """Get indicator categories""" + return { + "categories": [ + {"key": key, "name": value["name"], "description": value["description"]} + for key, value in AVAILABLE_INDICATORS.items() + ] + } + + +@router.get("/category/{category}") +async def get_category_indicators(category: str): + """Get indicators in a specific category""" + if category not in AVAILABLE_INDICATORS: + raise HTTPException(status_code=404, detail=f"Category '{category}' not found") + + return AVAILABLE_INDICATORS[category] + + +@router.get("/{indicator_id}") +async def get_indicator_details(indicator_id: str): + """Get detailed information about a specific indicator""" + for category in AVAILABLE_INDICATORS.values(): + for indicator in category.get("indicators", []): + if indicator["id"] == indicator_id: + return indicator + + raise HTTPException(status_code=404, detail=f"Indicator '{indicator_id}' not found") + + +@router.get("/default") +async def get_default_configuration(): + """Get recommended indicator configuration for gold trading""" + return { + "name": "Gold Trading Starter Pack", + "description": "Recommended indicators for gold day trading", + "configuration": DEFAULT_INDICATORS, + "explanation": { + "trend": "EMAs help identify trend direction", + "momentum": "RSI and MACD identify overbought/oversold conditions", + "volatility": "Bollinger Bands and ATR help with entry/exit zones", + "support_resistance": "Pivot points identify key support/resistance levels", + }, + "best_practices": [ + "Use 12/26 EMA crossover for trend confirmation", + "RSI above 70 = potential sell, below 30 = potential buy", + "MACD crossovers signal momentum changes", + "Bollinger Band squeeze precedes volatility expansion", + "Trade ATR breakouts for high probability moves", + ], + } + + +@router.get("/presets") +async def get_indicator_presets(): + """Get pre-configured indicator setups""" + return { + "presets": [ + { + "id": "scalping", + "name": "Scalping Setup (1-5 min)", + "indicators": [ + "ema_5", + "ema_10", + "rsi_14", + "macd", + "bb_20", + ], + "description": "Fast indicators for quick trade entries/exits", + }, + { + "id": "swing", + "name": "Swing Trading Setup (4h-1D)", + "indicators": [ + "sma_50", + "ema_200", + "rsi_14", + "macd", + "pivot_classic", + ], + "description": "Medium-term trend and momentum indicators", + }, + { + "id": "position", + "name": "Position Trading Setup (1D+)", + "indicators": [ + "sma_50", + "sma_200", + "rsi_14", + "bb_20", + "fibonacci", + ], + "description": "Long-term trend and support/resistance levels", + }, + { + "id": "volatility", + "name": "Volatility Focus Setup", + "indicators": [ + "bb_20", + "atr_14", + "kc_20", + "obv_20", + ], + "description": "For high volatility market conditions", + }, + { + "id": "momentum", + "name": "Momentum Focus Setup", + "indicators": [ + "rsi_14", + "stochastic_14", + "macd", + "kdj_9", + ], + "description": "For momentum-driven market moves", + }, + ] + } + + +@router.post("/preset/{preset_id}/apply") +async def apply_preset(preset_id: str, user_id: Optional[str] = Query(None)): + """Apply a pre-configured indicator preset""" + presets = await get_indicator_presets() + preset = next((p for p in presets["presets"] if p["id"] == preset_id), None) + + if not preset: + raise HTTPException(status_code=404, detail=f"Preset '{preset_id}' not found") + + # Store user configuration + if user_id: + USER_INDICATORS[user_id] = preset.copy() + + return { + "status": "preset_applied", + "preset": preset, + "applied_at": datetime.now().isoformat(), + } + + +@router.post("/custom") +async def create_custom_configuration( + indicators_list: List[str], config_name: str, user_id: Optional[str] = Query(None) +): + """Create a custom indicator configuration""" + # Validate all requested indicators exist + valid_indicators = [] + for cat in AVAILABLE_INDICATORS.values(): + for ind in cat.get("indicators", []): + valid_indicators.append(ind["id"]) + + invalid = [i for i in indicators_list if i not in valid_indicators] + if invalid: + raise HTTPException( + status_code=400, + detail=f"Invalid indicators: {invalid}", + ) + + config = { + "name": config_name, + "indicators": indicators_list, + "created_at": datetime.now().isoformat(), + "indicator_count": len(indicators_list), + } + + if user_id: + USER_INDICATORS[user_id] = config + + return { + "status": "configuration_created", + "configuration": config, + } + + +@router.get("/recommendations") +async def get_indicator_recommendations( + market_condition: str = Query("normal", regex="^(trending|ranging|volatile|calm)$"), + trading_style: str = Query("swing", regex="^(scalping|swing|position)$"), +): + """Get recommended indicators based on market conditions""" + recommendations = { + "trending": { + "best": ["ema_12_26_crossover", "atr_14", "obv_20"], + "supporting": ["pivot_points", "fibonacci"], + "avoid": ["stochastic", "rsi_only"], + "reasoning": "Use trend-following indicators in trending markets", + }, + "ranging": { + "best": ["rsi_14", "stochastic_14", "bb_20"], + "supporting": ["pivot_points"], + "avoid": ["moving_average_crossovers"], + "reasoning": "Use oscillators for overbought/oversold in ranging markets", + }, + "volatile": { + "best": ["atr_14", "bb_20", "kc_20"], + "supporting": ["ema_12_26"], + "avoid": ["simple_moving_averages"], + "reasoning": "Track volatility expansion with volatility indicators", + }, + "calm": { + "best": ["pivot_points", "fibonacci", "volume_profile"], + "supporting": ["rsi_14", "macd"], + "avoid": ["atr"], + "reasoning": "Focus on support/resistance levels when volatility is low", + }, + } + + timeframe_recommendations = { + "scalping": { + "periods": ["1m", "5m"], + "indicators": ["ema_5_10", "rsi_14", "macd"], + "setup": "Fast indicators for quick entries", + }, + "swing": { + "periods": ["4h", "1D"], + "indicators": ["ema_12_26", "rsi_14", "bb_20", "pivot_points"], + "setup": "Balanced trend and momentum", + }, + "position": { + "periods": ["1D", "1W"], + "indicators": ["sma_50_200", "rsi_14", "fibonacci"], + "setup": "Long-term trend following", + }, + } + + return { + "market_condition": market_condition, + "trading_style": trading_style, + "recommended_indicators": recommendations.get( + market_condition, recommendations["normal"] + ), + "timeframe_setup": timeframe_recommendations.get(trading_style), + } + + +@router.post("/calculate/{indicator}") +async def calculate_indicator( + indicator: str, + price_data: List[float], + period: int = Query(14, ge=2, le=200), +): + """ + Calculate indicator values (for testing/visualization) + + This would typically be called for real calculations + """ + if indicator == "rsi": + # Simplified RSI calculation + if len(price_data) < period: + raise HTTPException( + status_code=400, + detail=f"Need at least {period} data points", + ) + + changes = [price_data[i] - price_data[i - 1] for i in range(1, len(price_data))] + gains = [max(0, c) for c in changes] + losses = [abs(min(0, c)) for c in changes] + + avg_gain = sum(gains[-period:]) / period + avg_loss = sum(losses[-period:]) / period + + rsi = 100 - (100 / (1 + (avg_gain / avg_loss if avg_loss != 0 else 1))) + return {"indicator": indicator, "period": period, "value": rsi} + + raise HTTPException(status_code=400, detail=f"Indicator '{indicator}' calculation not implemented") + + +@router.get("/alerts/golden-cross") +async def get_golden_cross_alerts(): + """Get alerts for golden cross (50-day SMA crosses above 200-day SMA)""" + return { + "alert_type": "golden_cross", + "description": "50-day SMA crosses above 200-day SMA (bullish signal)", + "current_status": "monitoring", + "last_occurrence": "2024-11-10", + "signal_strength": "strong", + "recommended_action": "Consider long positions", + } + + +@router.get("/alerts/death-cross") +async def get_death_cross_alerts(): + """Get alerts for death cross (50-day SMA crosses below 200-day SMA)""" + return { + "alert_type": "death_cross", + "description": "50-day SMA crosses below 200-day SMA (bearish signal)", + "current_status": "monitoring", + "last_occurrence": None, + "signal_strength": None, + "recommended_action": "Monitor for potential bearish reversal", + } + + +@router.get("/alerts/divergence") +async def get_divergence_alerts(): + """Get alerts for price/indicator divergences""" + return { + "divergence_alerts": [ + { + "type": "bullish_divergence", + "indicator": "rsi", + "description": "Price makes lower low but RSI makes higher low", + "signal": "potential_uptrend_reversal", + "strength": "medium", + }, + { + "type": "bearish_divergence", + "indicator": "macd", + "description": "Price makes higher high but MACD makes lower high", + "signal": "potential_downtrend_reversal", + "strength": "high", + }, + ] + } + + +@router.get("/cheat-sheet") +async def get_indicator_cheat_sheet(): + """Get quick reference guide for all indicators""" + return { + "moving_averages": { + "ema_crossover": "Golden Cross (50 > 200) = bullish, Death Cross = bearish", + "price_cross_ma": "Price above MA = uptrend, Below = downtrend", + "ma_bounce": "Price bounces off MA = trend continuation", + }, + "oscillators": { + "rsi_above_70": "Overbought - look for reversals", + "rsi_below_30": "Oversold - look for bounces", + "rsi_divergence": "Price higher but RSI lower = bearish signal", + "macd_cross": "MACD above signal line = bullish", + }, + "volatility": { + "bb_squeeze": "Low volatility - breakout coming soon", + "bb_expansion": "High volatility - expect big moves", + "atr_low": "Low volatility period", + "atr_high": "High volatility period", + }, + "support_resistance": { + "pivot_s1": "First support level", + "pivot_r1": "First resistance level", + "fibonacci_618": "Most important retracement level", + }, + } diff --git a/backend/app/main.py b/backend/app/main.py index 82a3a5c..f18b409 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -7,7 +7,7 @@ from app.streaming.live_store import periodic_flush, periodic_maintenance import asyncio # Newly added routers -from app.api import account, performance, status, settings_api, prompts, daily_helper, analytics, economic_calendar +from app.api import account, performance, status, settings_api, prompts, daily_helper, analytics, economic_calendar, indicators app = FastAPI( title=settings.APP_NAME, @@ -44,6 +44,7 @@ app.include_router(prompts.router, prefix="/api") app.include_router(daily_helper.router) app.include_router(analytics.router) app.include_router(economic_calendar.router) +app.include_router(indicators.router) @app.on_event("startup") diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fb69766..0a12430 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,6 +19,7 @@ import AnalyticsDashboard from './components/AnalyticsDashboard' // Phase 4: Economic Calendar & Advanced Features import EconomicCalendar from './components/EconomicCalendar' +import AdvancedIndicatorsPanel from './components/AdvancedIndicatorsPanel' function Tabs({ tabs, active, onChange }: { tabs: string[]; active: string; onChange: (t: string) => void }) { return ( @@ -33,7 +34,7 @@ function Tabs({ tabs, active, onChange }: { tabs: string[]; active: string; onCh } export default function App() { - const [activeTab, setActiveTab] = useState<'Live' | 'Account' | 'Equity' | 'Decisions' | 'Analytics' | 'Economic Calendar' | 'Settings' | 'Prompts' | 'Daily Helper'>('Live') + const [activeTab, setActiveTab] = useState<'Live' | 'Account' | 'Equity' | 'Decisions' | 'Analytics' | 'Economic Calendar' | 'Indicators' | 'Settings' | 'Prompts' | 'Daily Helper'>('Live') const [backendStatus, setBackendStatus] = useState(null) const [showProfileSetup, setShowProfileSetup] = useState(false) @@ -50,7 +51,7 @@ export default function App() { return () => { mounted = false } }, []) - const tabs = ['Live', 'Account', 'Equity', 'Decisions', 'Analytics', 'Economic Calendar', 'Daily Helper', 'Settings', 'Prompts'] + const tabs = ['Live', 'Account', 'Equity', 'Decisions', 'Analytics', 'Economic Calendar', 'Indicators', 'Daily Helper', 'Settings', 'Prompts'] return (
@@ -88,6 +89,7 @@ export default function App() { {activeTab === 'Decisions' && } {activeTab === 'Analytics' && } {activeTab === 'Economic Calendar' && } + {activeTab === 'Indicators' && } {activeTab === 'Daily Helper' && (
diff --git a/frontend/src/components/AdvancedIndicatorsPanel.tsx b/frontend/src/components/AdvancedIndicatorsPanel.tsx new file mode 100644 index 0000000..a4ba811 --- /dev/null +++ b/frontend/src/components/AdvancedIndicatorsPanel.tsx @@ -0,0 +1,340 @@ +import { useEffect, useState } from 'react'; +import { Settings, Zap, BookOpen, Grid3X3, Check } from 'lucide-react'; +import axios from 'axios'; + +interface Indicator { + id: string; + name: string; + periods?: number[]; + default_period?: number; + description?: string; + type: string; +} + +interface IndicatorCategory { + name: string; + description: string; + indicators: Indicator[]; +} + +interface Preset { + id: string; + name: string; + indicators: string[]; + description: string; +} + +export default function AdvancedIndicatorsPanel() { + const [categories, setCategories] = useState>({}); + const [presets, setPresets] = useState([]); + const [selectedIndicators, setSelectedIndicators] = useState>(new Set()); + const [activeTab, setActiveTab] = useState<'presets' | 'custom' | 'guide'>('presets'); + const [loading, setLoading] = useState(true); + const [selectedPreset, setSelectedPreset] = useState(null); + const [cheatSheet, setCheatSheet] = useState(null); + + // Fetch indicators and presets + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + + // Fetch available indicators + const indicatorsResponse = await axios.get('/api/indicators/available'); + setCategories(indicatorsResponse.data.categories || {}); + + // Fetch presets + const presetsResponse = await axios.get('/api/indicators/presets'); + setPresets(presetsResponse.data.presets || []); + + // Fetch cheat sheet + const cheatSheetResponse = await axios.get('/api/indicators/cheat-sheet'); + setCheatSheet(cheatSheetResponse.data || {}); + } catch (error) { + console.error('Error fetching indicators data:', error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + const handleIndicatorToggle = (indicatorId: string) => { + const newSet = new Set(selectedIndicators); + if (newSet.has(indicatorId)) { + newSet.delete(indicatorId); + } else { + newSet.add(indicatorId); + } + setSelectedIndicators(newSet); + }; + + const handlePresetSelect = (presetId: string) => { + const preset = presets.find((p) => p.id === presetId); + if (preset) { + setSelectedIndicators(new Set(preset.indicators)); + setSelectedPreset(presetId); + } + }; + + const getTypeColor = (type: string): string => { + switch (type) { + case 'trend': + return 'bg-blue-900 text-blue-300'; + case 'momentum': + return 'bg-purple-900 text-purple-300'; + case 'volatility': + return 'bg-orange-900 text-orange-300'; + case 'level': + return 'bg-green-900 text-green-300'; + case 'volume': + return 'bg-pink-900 text-pink-300'; + default: + return 'bg-gray-700 text-gray-300'; + } + }; + + if (loading) { + return ( +
+

+ + Advanced Indicators +

+
Loading indicators...
+
+ ); + } + + return ( +
+
+
+

+ + Advanced Technical Indicators +

+ + {/* Tabs */} +
+ + + +
+
+ + {/* Presets Tab */} + {activeTab === 'presets' && ( +
+ {presets.map((preset) => ( +
handlePresetSelect(preset.id)} + className={`p-4 rounded-lg border cursor-pointer transition ${ + selectedPreset === preset.id + ? 'bg-blue-900 border-blue-500' + : 'bg-dark-bg border-dark-border hover:border-blue-500' + }`} + > +
+
+

+ {preset.name} + {selectedPreset === preset.id && } +

+

{preset.description}

+
+ + {preset.indicators.length} indicators + +
+ +
+ {preset.indicators.map((ind) => ( + + {ind} + + ))} +
+
+ ))} +
+ )} + + {/* Custom Setup Tab */} + {activeTab === 'custom' && ( +
+ {Object.entries(categories).map(([categoryKey, category]) => ( +
+

+ {category.name} +

+

{category.description}

+ +
+ {category.indicators.map((indicator) => ( + + ))} +
+
+ ))} + + {selectedIndicators.size > 0 && ( +
+

Configuration Ready

+

+ You've selected {selectedIndicators.size} indicator(s). Click below to apply. +

+ +
+ )} +
+ )} + + {/* Quick Guide Tab */} + {activeTab === 'guide' && cheatSheet && ( +
+ {Object.entries(cheatSheet).map(([key, value]) => ( +
+

+ {key.replace(/_/g, ' ')} +

+
+ {typeof value === 'object' && + !Array.isArray(value) && + Object.entries(value).map(([subKey, subValue]) => ( +
+

{subKey}

+

{String(subValue)}

+
+ ))} +
+
+ ))} +
+ )} +
+ + {/* Selected Indicators Summary */} + {selectedIndicators.size > 0 && activeTab !== 'guide' && ( +
+

Currently Selected

+
+ {Array.from(selectedIndicators).map((ind) => ( +
+ {ind} + +
+ ))} +
+
+ )} + + {/* Indicator Types Legend */} +
+

Indicator Types

+
+
+
+ Trend +
+
+
+ Momentum +
+
+
+ Volatility +
+
+
+ Support/Resistance +
+
+
+ Volume +
+
+
+ + {/* Best Practices */} +
+

Pro Tips

+
    +
  • ✓ Use 2-3 indicators maximum to avoid signal conflicts
  • +
  • ✓ Combine different indicator types (trend + momentum + volatility)
  • +
  • ✓ Scalping: Use fast periods (5, 10, 14)
  • +
  • ✓ Swing/Position: Use standard periods (20, 50, 200)
  • +
  • ✓ Always confirm signals with price action and volume
  • +
  • ✓ Use presets as a starting point, customize based on your style
  • +
+
+
+ ); +}