diff --git a/database/models.py b/database/models.py index fa4b71c..c02940a 100644 --- a/database/models.py +++ b/database/models.py @@ -241,6 +241,72 @@ class BotPerformance(Base): return f"" +class StrategyRun(Base): + """Strategy Execution Sessions - For tracking strategy backtesting and analysis runs""" + __tablename__ = 'strategy_runs' + + id = Column(Integer, primary_key=True) + strategy_name = Column(String(100), nullable=False) + symbol = Column(String(20), nullable=False) + timeframe = Column(String(5), nullable=False) + start_time = Column(DateTime(timezone=True), nullable=False) + end_time = Column(DateTime(timezone=True)) + status = Column(String(20), nullable=False, default='running') # running, completed, failed + config = Column(JSONB) # Strategy configuration parameters + run_metadata = Column(JSONB) # Run metadata (backtesting params, etc.) + total_signals = Column(Integer, default=0) + created_at = Column(DateTime(timezone=True), default=func.now()) + updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) + + # Relationships + strategy_signals = relationship("StrategySignal", back_populates="strategy_run", cascade="all, delete-orphan") + + __table_args__ = ( + CheckConstraint("status IN ('running', 'completed', 'failed')", name='chk_strategy_run_status'), + Index('idx_strategy_runs_strategy_time', 'strategy_name', 'start_time'), + Index('idx_strategy_runs_symbol', 'symbol'), + Index('idx_strategy_runs_status', 'status'), + Index('idx_strategy_runs_timeframe', 'timeframe'), + ) + + def __repr__(self): + return f"" + + +class StrategySignal(Base): + """Strategy Analysis Signals - Generated by strategies for analysis and backtesting (separate from bot signals)""" + __tablename__ = 'strategy_signals' + + id = Column(Integer, primary_key=True) + run_id = Column(Integer, ForeignKey('strategy_runs.id', ondelete='CASCADE'), nullable=False) + strategy_name = Column(String(100), nullable=False) + strategy_config = Column(JSONB) # Strategy configuration used for this signal + symbol = Column(String(20), nullable=False) + timeframe = Column(String(5), nullable=False) + timestamp = Column(DateTime(timezone=True), nullable=False) + signal_type = Column(String(20), nullable=False) # buy, sell, hold, entry_long, exit_long, etc. + price = Column(DECIMAL(18, 8)) + confidence = Column(DECIMAL(5, 4)) # 0.0000 to 1.0000 + signal_metadata = Column(JSONB) # Additional signal metadata (indicator values, etc.) + created_at = Column(DateTime(timezone=True), default=func.now()) + + # Relationships + strategy_run = relationship("StrategyRun", back_populates="strategy_signals") + + __table_args__ = ( + CheckConstraint("signal_type IN ('buy', 'sell', 'hold', 'entry_long', 'exit_long', 'entry_short', 'exit_short', 'stop_loss', 'take_profit')", name='chk_strategy_signal_type'), + CheckConstraint("confidence >= 0 AND confidence <= 1", name='chk_strategy_confidence'), + Index('idx_strategy_signals_strategy_time', 'strategy_name', 'timestamp'), + Index('idx_strategy_signals_run_time', 'run_id', 'timestamp'), + Index('idx_strategy_signals_symbol_timeframe', 'symbol', 'timeframe'), + Index('idx_strategy_signals_type', 'signal_type'), + Index('idx_strategy_signals_timestamp', 'timestamp'), + ) + + def __repr__(self): + return f"" + + # Reference tables for system configuration class SupportedTimeframe(Base): """Supported timeframes configuration""" @@ -277,6 +343,8 @@ def get_model_by_table_name(table_name: str): 'signals': Signal, 'trades': Trade, 'bot_performance': BotPerformance, + 'strategy_runs': StrategyRun, + 'strategy_signals': StrategySignal, 'supported_timeframes': SupportedTimeframe, 'supported_exchanges': SupportedExchange, } diff --git a/database/operations.py b/database/operations.py index 8f77a9e..9d39b70 100644 --- a/database/operations.py +++ b/database/operations.py @@ -14,6 +14,7 @@ from .repositories import ( BotRepository, MarketDataRepository, RawTradeRepository, + StrategyRepository, DatabaseOperationError, ) @@ -33,6 +34,7 @@ class DatabaseOperations: self.bots = BotRepository(logger) self.market_data = MarketDataRepository(logger) self.raw_trades = RawTradeRepository(logger) + self.strategies = StrategyRepository(logger) def health_check(self) -> bool: """ @@ -64,7 +66,8 @@ class DatabaseOperations: 'repositories': { 'bots': 'BotRepository', 'market_data': 'MarketDataRepository', - 'raw_trades': 'RawTradeRepository' + 'raw_trades': 'RawTradeRepository', + 'strategies': 'StrategyRepository' } } @@ -73,6 +76,8 @@ class DatabaseOperations: stats['bot_count'] = session.execute(text("SELECT COUNT(*) FROM bots")).scalar_one() stats['candle_count'] = session.execute(text("SELECT COUNT(*) FROM market_data")).scalar_one() stats['raw_trade_count'] = session.execute(text("SELECT COUNT(*) FROM raw_trades")).scalar_one() + stats['strategy_runs_count'] = session.execute(text("SELECT COUNT(*) FROM strategy_runs")).scalar_one() + stats['strategy_signals_count'] = session.execute(text("SELECT COUNT(*) FROM strategy_signals")).scalar_one() return stats diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index a9b982e..cbf6b73 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -5,6 +5,7 @@ from .base_repository import BaseRepository, DatabaseOperationError from .bot_repository import BotRepository from .market_data_repository import MarketDataRepository from .raw_trade_repository import RawTradeRepository +from .strategy_repository import StrategyRepository __all__ = [ "BaseRepository", @@ -12,4 +13,5 @@ __all__ = [ "BotRepository", "MarketDataRepository", "RawTradeRepository", + "StrategyRepository", ] \ No newline at end of file diff --git a/database/repositories/strategy_repository.py b/database/repositories/strategy_repository.py new file mode 100644 index 0000000..8ba9483 --- /dev/null +++ b/database/repositories/strategy_repository.py @@ -0,0 +1,252 @@ +"""Repository for strategy_signals and strategy_runs table operations.""" + +from datetime import datetime, timedelta +from typing import Dict, Any, Optional, List +from decimal import Decimal + +from sqlalchemy import desc, and_, func +from sqlalchemy.orm import joinedload + +from ..models import StrategySignal, StrategyRun +from strategies.data_types import StrategySignal as StrategySignalDataType, StrategyResult +from .base_repository import BaseRepository, DatabaseOperationError + + +class StrategyRepository(BaseRepository): + """Repository for strategy_signals and strategy_runs table operations.""" + + # Strategy Run Operations + def create_strategy_run(self, run_data: Dict[str, Any]) -> StrategyRun: + """ + Create a new strategy run session. + + Args: + run_data: Dictionary containing run information (strategy_name, symbol, timeframe, etc.) + + Returns: + The newly created StrategyRun object + """ + try: + with self.get_session() as session: + new_run = StrategyRun(**run_data) + session.add(new_run) + session.commit() + session.refresh(new_run) + self.log_info(f"Created strategy run: {new_run.strategy_name} for {new_run.symbol}") + return new_run + except Exception as e: + self.log_error(f"Error creating strategy run: {e}") + raise DatabaseOperationError(f"Failed to create strategy run: {e}") + + def get_strategy_run_by_id(self, run_id: int) -> Optional[StrategyRun]: + """Get a strategy run by its ID.""" + try: + with self.get_session() as session: + return session.query(StrategyRun).filter(StrategyRun.id == run_id).first() + except Exception as e: + self.log_error(f"Error getting strategy run by ID {run_id}: {e}") + raise DatabaseOperationError(f"Failed to get strategy run by ID: {e}") + + def update_strategy_run(self, run_id: int, update_data: Dict[str, Any]) -> Optional[StrategyRun]: + """Update a strategy run's information.""" + try: + with self.get_session() as session: + strategy_run = session.query(StrategyRun).filter(StrategyRun.id == run_id).first() + if strategy_run: + for key, value in update_data.items(): + setattr(strategy_run, key, value) + session.commit() + session.refresh(strategy_run) + self.log_info(f"Updated strategy run {run_id}") + return strategy_run + return None + except Exception as e: + self.log_error(f"Error updating strategy run {run_id}: {e}") + raise DatabaseOperationError(f"Failed to update strategy run: {e}") + + def complete_strategy_run(self, run_id: int, total_signals: int) -> bool: + """Mark a strategy run as completed.""" + try: + update_data = { + 'status': 'completed', + 'end_time': datetime.now(datetime.timezone.utc), + 'total_signals': total_signals + } + result = self.update_strategy_run(run_id, update_data) + return result is not None + except Exception as e: + self.log_error(f"Error completing strategy run {run_id}: {e}") + return False + + # Strategy Signal Operations + def store_strategy_signals(self, run_id: int, strategy_results: List[StrategyResult]) -> int: + """ + Store multiple strategy signals from strategy results. + + Args: + run_id: The strategy run ID these signals belong to + strategy_results: List of StrategyResult objects containing signals + + Returns: + Number of signals stored + """ + try: + signals_stored = 0 + with self.get_session() as session: + for result in strategy_results: + for signal in result.signals: + strategy_signal = StrategySignal( + run_id=run_id, + strategy_name=result.strategy_name, + strategy_config=None, # Could be populated from StrategyRun.config + symbol=signal.symbol, + timeframe=signal.timeframe, + timestamp=signal.timestamp, + signal_type=signal.signal_type.value, + price=Decimal(str(signal.price)), + confidence=Decimal(str(signal.confidence)), + signal_metadata={ + 'indicators_used': result.indicators_used, + 'metadata': signal.metadata or {} + } + ) + session.add(strategy_signal) + signals_stored += 1 + + session.commit() + self.log_info(f"Stored {signals_stored} strategy signals for run {run_id}") + return signals_stored + + except Exception as e: + self.log_error(f"Error storing strategy signals for run {run_id}: {e}") + raise DatabaseOperationError(f"Failed to store strategy signals: {e}") + + def get_strategy_signals( + self, + run_id: Optional[int] = None, + strategy_name: Optional[str] = None, + symbol: Optional[str] = None, + timeframe: Optional[str] = None, + start_time: Optional[datetime] = None, + end_time: Optional[datetime] = None, + signal_type: Optional[str] = None, + limit: Optional[int] = None + ) -> List[StrategySignal]: + """ + Retrieve strategy signals with flexible filtering. + + Args: + run_id: Filter by strategy run ID + strategy_name: Filter by strategy name + symbol: Filter by trading symbol + timeframe: Filter by timeframe + start_time: Filter signals after this time + end_time: Filter signals before this time + signal_type: Filter by signal type + limit: Maximum number of signals to return + + Returns: + List of StrategySignal objects + """ + try: + with self.get_session() as session: + query = session.query(StrategySignal) + + # Apply filters + if run_id is not None: + query = query.filter(StrategySignal.run_id == run_id) + if strategy_name: + query = query.filter(StrategySignal.strategy_name == strategy_name) + if symbol: + query = query.filter(StrategySignal.symbol == symbol) + if timeframe: + query = query.filter(StrategySignal.timeframe == timeframe) + if start_time: + query = query.filter(StrategySignal.timestamp >= start_time) + if end_time: + query = query.filter(StrategySignal.timestamp <= end_time) + if signal_type: + query = query.filter(StrategySignal.signal_type == signal_type) + + # Order by timestamp descending + query = query.order_by(desc(StrategySignal.timestamp)) + + # Apply limit + if limit: + query = query.limit(limit) + + return query.all() + + except Exception as e: + self.log_error(f"Error retrieving strategy signals: {e}") + raise DatabaseOperationError(f"Failed to retrieve strategy signals: {e}") + + def get_strategy_signal_stats(self, run_id: Optional[int] = None) -> Dict[str, Any]: + """Get statistics about strategy signals.""" + try: + with self.get_session() as session: + query = session.query(StrategySignal) + + if run_id is not None: + query = query.filter(StrategySignal.run_id == run_id) + + # Get basic counts by signal type + signal_counts = session.query( + StrategySignal.signal_type, + func.count(StrategySignal.id).label('count') + ).group_by(StrategySignal.signal_type) + + if run_id is not None: + signal_counts = signal_counts.filter(StrategySignal.run_id == run_id) + + counts_dict = {signal_type: count for signal_type, count in signal_counts.all()} + + # Get total signals + total_signals = query.count() + + # Get average confidence + avg_confidence = session.query(func.avg(StrategySignal.confidence)).scalar() + + return { + 'total_signals': total_signals, + 'signal_counts': counts_dict, + 'average_confidence': float(avg_confidence) if avg_confidence else 0.0, + 'run_id': run_id + } + + except Exception as e: + self.log_error(f"Error getting strategy signal stats: {e}") + raise DatabaseOperationError(f"Failed to get strategy signal stats: {e}") + + # Data Retention and Cleanup + def cleanup_old_strategy_data(self, days_to_keep: int = 30) -> Dict[str, int]: + """ + Clean up old strategy signals and runs to prevent table bloat. + + Args: + days_to_keep: Number of days to retain data + + Returns: + Dictionary with counts of deleted records + """ + try: + cutoff_date = datetime.now(datetime.timezone.utc) - timedelta(days=days_to_keep) + + with self.get_session() as session: + # Delete old strategy runs (and their signals via CASCADE) + deleted_runs = session.query(StrategyRun).filter( + StrategyRun.created_at < cutoff_date, + StrategyRun.status == 'completed' # Only delete completed runs + ).delete(synchronize_session=False) + + session.commit() + + self.log_info(f"Cleaned up {deleted_runs} old strategy runs and their signals") + return { + 'deleted_runs': deleted_runs, + 'cutoff_date': cutoff_date.isoformat() + } + + except Exception as e: + self.log_error(f"Error cleaning up old strategy data: {e}") + raise DatabaseOperationError(f"Failed to cleanup old strategy data: {e}") \ No newline at end of file diff --git a/docs/architecture.md b/docs/architecture.md index 26d4356..cdc105b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -65,12 +65,13 @@ class BotManager: ``` ### 4. Database -**Responsibility**: Data persistence and storage +**Responsibility**: Data persistence and storage for market data, bot configurations, trading signals, and strategy analysis results. **Implementation**: `database/` **Key Features**: - PostgreSQL with TimescaleDB extension for time-series data - SQLAlchemy for ORM and schema management - Alembic for database migrations +- Dedicated tables for strategy backtesting (e.g., `strategy_signals`, `strategy_runs`) separate from live trading `signals`. - See [Database Operations Documentation (`modules/database_operations.md`)](./modules/database_operations.md) for details. ### 5. Backtesting Engine diff --git a/docs/modules/database_operations.md b/docs/modules/database_operations.md index 8f7f30d..e435498 100644 --- a/docs/modules/database_operations.md +++ b/docs/modules/database_operations.md @@ -27,23 +27,23 @@ The Database Operations module (`database/operations.py`) provides a clean, cent ## Architecture ``` -┌─────────────────────────────────────────────────────────────┐ -│ DatabaseOperations │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Health Check & Stats │ │ -│ │ • Connection health monitoring │ │ -│ │ • Database statistics │ │ -│ │ • Performance metrics │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │ -│ │MarketDataRepo │ │RawTradeRepo │ │ BotRepo │ │ -│ │ │ │ │ │ │ │ -│ │ • upsert_candle │ │ • insert_data │ │ • add │ │ -│ │ • get_candles │ │ • get_trades │ │ • get_by_id │ │ -│ │ • get_latest │ │ • raw_websocket │ │ • update/delete│ │ -│ └─────────────────┘ └─────────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────────┘ +┌───────────────────────────────────────────────────────────────────┐ +│ DatabaseOperations │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ Health Check & Stats │ │ +│ │ • Connection health monitoring │ │ +│ │ • Database statistics │ │ +│ │ • Performance metrics │ │ +│ └───────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌───────────────────┐ │ +│ │MarketDataRepo │ │RawTradeRepo │ │ BotRepo │ │ StrategyRepo │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ • upsert_candle │ │ • insert_data │ │ • add │ │ • create_run │ │ +│ │ • get_candles │ │ • get_trades │ │ • get_by_id │ │ • store_signals │ │ +│ │ • get_latest │ │ • raw_websocket │ │ • update/delete│ │ • get_signals/stats │ │ +│ └─────────────────┘ └─────────────────┘ └──────────────┘ └───────────────────┘ │ +└───────────────────────────────────────────────────────────────────┘ │ ┌─────────────────┐ │ BaseRepository │ @@ -152,6 +152,8 @@ stats = db.get_stats() print(f"Bots: {stats['bot_count']:,}") print(f"Candles: {stats['candle_count']:,}") print(f"Raw trades: {stats['raw_trade_count']:,}") +print(f"Strategy runs: {stats['strategy_runs_count']:,}") +print(f"Strategy signals: {stats['strategy_signals_count']:,}") print(f"Health: {stats['healthy']}") ``` @@ -369,6 +371,166 @@ print(f"Raw Trades Table Size: {raw_stats.get('table_size')}") print(f"Total Raw Records: {raw_stats.get('total_records')}") ``` +### StrategyRepository + +Repository for `strategy_signals` and `strategy_runs` table operations. + +#### Methods + +##### `create_strategy_run(run_data: Dict[str, Any]) -> StrategyRun` + +Create a new strategy run session. + +**Parameters:** +- `run_data`: Dictionary containing run information (`strategy_name`, `symbol`, `timeframe`, `start_time`, `status`, `config`, `run_metadata`) + +**Returns:** The newly created `StrategyRun` object. + +```python +from datetime import datetime, timezone + +run_data = { + "strategy_name": "EMA Crossover Backtest", + "symbol": "BTC-USDT", + "timeframe": "1h", + "start_time": datetime(2023, 1, 1, tzinfo=timezone.utc), + "status": "running", + "config": {"fast_ema": 12, "slow_ema": 26}, + "run_metadata": {"backtest_period": "2023-Q1"} +} +new_run = db.strategies.create_strategy_run(run_data) +print(f"Created strategy run with ID: {new_run.id}") +``` + +##### `get_strategy_run_by_id(run_id: int) -> Optional[StrategyRun]` + +Get a strategy run by its ID. + +```python +run = db.strategies.get_strategy_run_by_id(1) +if run: + print(f"Found strategy run: {run.strategy_name} ({run.status})") +``` + +##### `update_strategy_run(run_id: int, update_data: Dict[str, Any]) -> Optional[StrategyRun]` + +Update a strategy run's information. + +```python +update_payload = {"status": "completed", "total_signals": 150} +updated_run = db.strategies.update_strategy_run(1, update_payload) +if updated_run: + print(f"Strategy run status updated to: {updated_run.status}") +``` + +##### `complete_strategy_run(run_id: int, total_signals: int) -> bool` + +Mark a strategy run as completed. + +**Parameters:** +- `run_id`: The ID of the strategy run to complete. +- `total_signals`: The total number of signals generated during the run. + +**Returns:** `True` if successful, `False` otherwise. + +```python +success = db.strategies.complete_strategy_run(1, 150) +if success: + print("Strategy run marked as completed.") +``` + +##### `store_strategy_signals(run_id: int, strategy_results: List[StrategyResult]) -> int` + +Store multiple strategy signals from strategy results. + +**Parameters:** +- `run_id`: The strategy run ID these signals belong to. +- `strategy_results`: List of `StrategyResult` objects containing signals. + +**Returns:** Number of signals stored. + +```python +from strategies.data_types import StrategyResult, StrategySignal, SignalType +from datetime import datetime, timezone +from decimal import Decimal + +signal1 = StrategySignal( + timestamp=datetime.now(timezone.utc), + symbol="BTC-USDT", + timeframe="1h", + signal_type=SignalType.BUY, + price=Decimal("50000.0"), + confidence=Decimal("0.9") +) +result1 = StrategyResult( + timestamp=datetime.now(timezone.utc), + symbol="BTC-USDT", + timeframe="1h", + strategy_name="EMA Crossover", + signals=[signal1], + indicators_used={"ema_fast": 49900.0, "ema_slow": 50100.0} +) + +signals_stored = db.strategies.store_strategy_signals(new_run.id, [result1]) +print(f"Stored {signals_stored} signals.") +``` + +##### `get_strategy_signals(run_id: Optional[int] = None, strategy_name: Optional[str] = None, symbol: Optional[str] = None, timeframe: Optional[str] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, signal_type: Optional[str] = None, limit: Optional[int] = None) -> List[StrategySignal]` + +Retrieve strategy signals with flexible filtering. + +**Parameters:** +- `run_id`: Filter by strategy run ID. +- `strategy_name`: Filter by strategy name. +- `symbol`: Filter by trading symbol. +- `timeframe`: Filter by timeframe. +- `start_time`: Filter signals after this time. +- `end_time`: Filter signals before this time. +- `signal_type`: Filter by signal type (e.g., "buy", "sell", "entry_long"). +- `limit`: Maximum number of signals to return. + +**Returns:** List of `StrategySignal` objects. + +```python +signals = db.strategies.get_strategy_signals( + run_id=new_run.id, + signal_type="buy", + limit=10 +) +for signal in signals: + print(f"Signal: {signal.signal_type} at {signal.price} on {signal.timestamp}") +``` + +##### `get_strategy_signal_stats(run_id: Optional[int] = None) -> Dict[str, Any]` + +Get statistics about strategy signals. + +**Parameters:** +- `run_id`: Optional run ID to get stats for a specific run. + +**Returns:** Dictionary with statistics like total signals, counts by type, average confidence. + +```python +signal_stats = db.strategies.get_strategy_signal_stats(run_id=new_run.id) +print(f"Total Signals: {signal_stats['total_signals']}") +print(f"Signal Counts: {signal_stats['signal_counts']}") +print(f"Average Confidence: {signal_stats['average_confidence']:.2f}") +``` + +##### `cleanup_old_strategy_data(days_to_keep: int = 30) -> Dict[str, int]` + +Clean up old strategy signals and runs. + +**Parameters:** +- `days_to_keep`: Number of days to retain completed strategy runs and their signals. + +**Returns:** Dictionary with counts of deleted runs and signals. + +```python +deleted_info = db.strategies.cleanup_old_strategy_data(days_to_keep=60) +print(f"Deleted {deleted_info['deleted_runs']} old strategy runs.") +``` + ## Error Handling The database operations module includes comprehensive error handling with custom exceptions. diff --git a/tasks/4.0-strategy-engine-foundation.md b/tasks/4.0-strategy-engine-foundation.md index 137fdf7..d76c540 100644 --- a/tasks/4.0-strategy-engine-foundation.md +++ b/tasks/4.0-strategy-engine-foundation.md @@ -63,6 +63,16 @@ - **Reasoning**: Ensures consistency with the `BaseStrategy` abstract class, which now requires `strategy_name` during initialization, providing a clear identifier for each strategy instance. - **Impact**: All strategy implementations now correctly initialize their `strategy_name` via the base class, standardizing strategy identification across the engine. +### 5. Database Schema Design for Strategy Analysis +- **Decision**: Created separate `strategy_signals` and `strategy_runs` tables distinct from the existing `signals` table used for bot operations. +- **Reasoning**: Maintains clear separation between live bot trading signals and strategy analysis/backtesting data, avoiding conflicts and ensuring data integrity for different use cases. +- **Impact**: Strategy analysis can be performed independently without affecting live trading operations, with dedicated tables optimized for analytical queries and data retention policies. + +### 6. Repository Pattern Integration +- **Decision**: Implemented `StrategyRepository` following the established `BaseRepository` pattern and integrated it into the centralized `DatabaseOperations` class. +- **Reasoning**: Maintains consistency with existing database access patterns, ensures proper session management, and provides a clean API for strategy data operations. +- **Impact**: All strategy database operations follow the same patterns as other modules, with proper error handling, logging, and transaction management. + ## Tasks - [x] 1.0 Core Strategy Foundation Setup @@ -88,16 +98,16 @@ - [x] 2.8 Implement strategy parameter validation and default value handling - [x] 2.9 Add configuration export/import functionality for strategy sharing -- [ ] 3.0 Database Schema and Repository Layer - - [ ] 3.1 Create new `strategy_signals` table migration (separate from existing `signals` table for bot operations) - - [ ] 3.2 Design `strategy_signals` table with fields: strategy_name, strategy_config, symbol, timeframe, timestamp, signal_type, price, confidence, metadata, run_id - - [ ] 3.3 Create `strategy_runs` table to track strategy execution sessions for backtesting and analysis - - [ ] 3.4 Implement `StrategyRepository` class in `database/repositories/strategy_repository.py` following repository pattern - - [ ] 3.5 Add strategy repository methods for signal storage, retrieval, and batch operations - - [ ] 3.6 Update `database/operations.py` to include strategy operations access - - [ ] 3.7 Create database indexes for optimal strategy signal queries (strategy_name, symbol, timeframe, timestamp) - - [ ] 3.8 Add data retention policies for strategy signals (configurable cleanup of old analysis data) - - [ ] 3.9 Implement strategy signal aggregation queries for performance analysis +- [x] 3.0 Database Schema and Repository Layer + - [x] 3.1 Create new `strategy_signals` table migration (separate from existing `signals` table for bot operations) + - [x] 3.2 Design `strategy_signals` table with fields: strategy_name, strategy_config, symbol, timeframe, timestamp, signal_type, price, confidence, signal_metadata, run_id + - [x] 3.3 Create `strategy_runs` table to track strategy execution sessions for backtesting and analysis + - [x] 3.4 Implement `StrategyRepository` class in `database/repositories/strategy_repository.py` following repository pattern + - [x] 3.5 Add strategy repository methods for signal storage, retrieval, and batch operations + - [x] 3.6 Update `database/operations.py` to include strategy operations access + - [x] 3.7 Create database indexes for optimal strategy signal queries (strategy_name, symbol, timeframe, timestamp) + - [x] 3.8 Add data retention policies for strategy signals (configurable cleanup of old analysis data) + - [x] 3.9 Implement strategy signal aggregation queries for performance analysis - [ ] 4.0 Strategy Data Integration - [ ] 4.1 Create `StrategyDataIntegrator` class in new `strategies/data_integration.py` module