feat: implement strategy metadata and dashboard simulation panel
- Added display_name and description to BaseStrategy - Updated MA44 and MA125 strategies with metadata - Added /api/v1/strategies endpoint for dynamic discovery - Added Strategy Simulation panel to dashboard with date picker and tooltips - Implemented JS polling for backtest results in dashboard - Added performance test scripts and DB connection guide - Expanded indicator config to all 15 timeframes
This commit is contained in:
68
src/strategies/base.py
Normal file
68
src/strategies/base.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""
|
||||
Base Strategy Interface
|
||||
All strategies must inherit from this class.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any, List, Optional
|
||||
from enum import Enum
|
||||
|
||||
class SignalType(Enum):
|
||||
OPEN_LONG = "open_long"
|
||||
OPEN_SHORT = "open_short"
|
||||
CLOSE_LONG = "close_long"
|
||||
CLOSE_SHORT = "close_short"
|
||||
HOLD = "hold"
|
||||
|
||||
@dataclass
|
||||
class StrategySignal:
|
||||
type: SignalType
|
||||
confidence: float
|
||||
reasoning: str
|
||||
|
||||
class BaseStrategy(ABC):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||
self.config = config or {}
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""Unique identifier for the strategy"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def required_indicators(self) -> List[str]:
|
||||
"""List of indicator names required by this strategy (e.g. ['ma44'])"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def display_name(self) -> str:
|
||||
"""User-friendly name for display in UI (e.g. 'MA44 Crossover')"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(self) -> str:
|
||||
"""Detailed description of how the strategy works"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def analyze(
|
||||
self,
|
||||
candle: Dict[str, Any],
|
||||
indicators: Dict[str, float],
|
||||
current_position: Optional[Dict[str, Any]] = None
|
||||
) -> StrategySignal:
|
||||
"""
|
||||
Analyze market data and return a trading signal.
|
||||
|
||||
Args:
|
||||
candle: Dictionary containing 'close', 'open', 'high', 'low', 'volume', 'time'
|
||||
indicators: Dictionary of pre-computed indicator values
|
||||
current_position: Details about current open position (if any)
|
||||
{'type': 'long'/'short', 'entry_price': float, 'size': float}
|
||||
"""
|
||||
pass
|
||||
63
src/strategies/ma125_strategy.py
Normal file
63
src/strategies/ma125_strategy.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""
|
||||
MA125 Strategy
|
||||
Simple trend following strategy.
|
||||
- Long when Price > MA125
|
||||
- Short when Price < MA125
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from .base import BaseStrategy, StrategySignal, SignalType
|
||||
|
||||
class MA125Strategy(BaseStrategy):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "ma125_strategy"
|
||||
|
||||
@property
|
||||
def required_indicators(self) -> List[str]:
|
||||
return ["ma125"]
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
return "MA125 Strategy"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Long-term trend following using 125-period moving average. Better for identifying major trends."
|
||||
|
||||
def analyze(
|
||||
self,
|
||||
candle: Dict[str, Any],
|
||||
indicators: Dict[str, float],
|
||||
current_position: Optional[Dict[str, Any]] = None
|
||||
) -> StrategySignal:
|
||||
|
||||
price = candle['close']
|
||||
ma125 = indicators.get('ma125')
|
||||
|
||||
if ma125 is None:
|
||||
return StrategySignal(SignalType.HOLD, 0.0, "MA125 not available")
|
||||
|
||||
# Current position state
|
||||
is_long = current_position and current_position.get('type') == 'long'
|
||||
is_short = current_position and current_position.get('type') == 'short'
|
||||
|
||||
# Logic: Price > MA125 -> Bullish
|
||||
if price > ma125:
|
||||
if is_long:
|
||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} > MA125 {ma125:.2f}. Stay Long.")
|
||||
elif is_short:
|
||||
return StrategySignal(SignalType.CLOSE_SHORT, 1.0, f"Price {price:.2f} crossed above MA125 {ma125:.2f}. Close Short.")
|
||||
else:
|
||||
return StrategySignal(SignalType.OPEN_LONG, 1.0, f"Price {price:.2f} > MA125 {ma125:.2f}. Open Long.")
|
||||
|
||||
# Logic: Price < MA125 -> Bearish
|
||||
elif price < ma125:
|
||||
if is_short:
|
||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} < MA125 {ma125:.2f}. Stay Short.")
|
||||
elif is_long:
|
||||
return StrategySignal(SignalType.CLOSE_LONG, 1.0, f"Price {price:.2f} crossed below MA125 {ma125:.2f}. Close Long.")
|
||||
else:
|
||||
return StrategySignal(SignalType.OPEN_SHORT, 1.0, f"Price {price:.2f} < MA125 {ma125:.2f}. Open Short.")
|
||||
|
||||
return StrategySignal(SignalType.HOLD, 0.0, "Price == MA125")
|
||||
63
src/strategies/ma44_strategy.py
Normal file
63
src/strategies/ma44_strategy.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""
|
||||
MA44 Strategy
|
||||
Simple trend following strategy.
|
||||
- Long when Price > MA44
|
||||
- Short when Price < MA44
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from .base import BaseStrategy, StrategySignal, SignalType
|
||||
|
||||
class MA44Strategy(BaseStrategy):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "ma44_strategy"
|
||||
|
||||
@property
|
||||
def required_indicators(self) -> List[str]:
|
||||
return ["ma44"]
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
return "MA44 Strategy"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Buy when price crosses above MA44, sell when below. Good for trending markets."
|
||||
|
||||
def analyze(
|
||||
self,
|
||||
candle: Dict[str, Any],
|
||||
indicators: Dict[str, float],
|
||||
current_position: Optional[Dict[str, Any]] = None
|
||||
) -> StrategySignal:
|
||||
|
||||
price = candle['close']
|
||||
ma44 = indicators.get('ma44')
|
||||
|
||||
if ma44 is None:
|
||||
return StrategySignal(SignalType.HOLD, 0.0, "MA44 not available")
|
||||
|
||||
# Current position state
|
||||
is_long = current_position and current_position.get('type') == 'long'
|
||||
is_short = current_position and current_position.get('type') == 'short'
|
||||
|
||||
# Logic: Price > MA44 -> Bullish
|
||||
if price > ma44:
|
||||
if is_long:
|
||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} > MA44 {ma44:.2f}. Stay Long.")
|
||||
elif is_short:
|
||||
return StrategySignal(SignalType.CLOSE_SHORT, 1.0, f"Price {price:.2f} crossed above MA44 {ma44:.2f}. Close Short.")
|
||||
else:
|
||||
return StrategySignal(SignalType.OPEN_LONG, 1.0, f"Price {price:.2f} > MA44 {ma44:.2f}. Open Long.")
|
||||
|
||||
# Logic: Price < MA44 -> Bearish
|
||||
elif price < ma44:
|
||||
if is_short:
|
||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} < MA44 {ma44:.2f}. Stay Short.")
|
||||
elif is_long:
|
||||
return StrategySignal(SignalType.CLOSE_LONG, 1.0, f"Price {price:.2f} crossed below MA44 {ma44:.2f}. Close Long.")
|
||||
else:
|
||||
return StrategySignal(SignalType.OPEN_SHORT, 1.0, f"Price {price:.2f} < MA44 {ma44:.2f}. Open Short.")
|
||||
|
||||
return StrategySignal(SignalType.HOLD, 0.0, "Price == MA44")
|
||||
Reference in New Issue
Block a user