4.0 - 3.0 Implement strategy analysis tables and repository for backtesting

- Added `StrategyRun` and `StrategySignal` models to track strategy execution sessions and generated signals, respectively, ensuring a clear separation from live trading data.
- Introduced `StrategyRepository` for managing database operations related to strategy runs and signals, including methods for creating, updating, and retrieving strategy data.
- Updated `DatabaseOperations` to integrate the new repository, enhancing the overall architecture and maintaining consistency with existing database access patterns.
- Enhanced documentation to reflect the new database schema and repository functionalities, ensuring clarity for future development and usage.

These changes establish a robust foundation for strategy analysis and backtesting, aligning with project goals for modularity, performance, and maintainability.
This commit is contained in:
Vasily.onl 2025-06-12 15:29:14 +08:00
parent d34da789ec
commit f09864d61b
7 changed files with 529 additions and 29 deletions

View File

@ -241,6 +241,72 @@ class BotPerformance(Base):
return f"<BotPerformance(Bot {self.bot_id} - Value: {self.total_value} - Win Rate: {self.win_rate:.2f}%)>"
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"<StrategyRun({self.strategy_name} - {self.symbol} - {self.status})>"
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"<StrategySignal({self.strategy_name} - {self.signal_type} - {self.price} - {self.confidence})>"
# 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,
}

View File

@ -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

View File

@ -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",
]

View File

@ -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}")

View File

@ -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

View File

@ -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.

View File

@ -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