Vasily.onl c9ae507bb7 Implement Incremental Trading Framework
- Introduced a comprehensive framework for incremental trading strategies, including modules for strategy execution, backtesting, and data processing.
- Added key components such as `IncTrader`, `IncBacktester`, and various trading strategies (e.g., `MetaTrendStrategy`, `BBRSStrategy`, `RandomStrategy`) to facilitate real-time trading and backtesting.
- Implemented a robust backtesting framework with configuration management, parallel execution, and result analysis capabilities.
- Developed an incremental indicators framework to support real-time data processing with constant memory usage.
- Enhanced documentation to provide clear usage examples and architecture overview, ensuring maintainability and ease of understanding for future development.
- Ensured compatibility with existing strategies and maintained a focus on performance and scalability throughout the implementation.
2025-05-28 16:29:48 +08:00

301 lines
12 KiB
Python

"""
Incremental Trader for backtesting incremental strategies.
This module provides the IncTrader class that manages a single incremental strategy
during backtesting, handling strategy execution, trade decisions, and performance tracking.
"""
import pandas as pd
import numpy as np
from typing import Dict, Optional, List, Any
import logging
# Use try/except for imports to handle both relative and absolute import scenarios
try:
from ..strategies.base import IncStrategyBase, IncStrategySignal
from .position import PositionManager, TradeRecord
except ImportError:
# Fallback for direct execution
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from strategies.base import IncStrategyBase, IncStrategySignal
from position import PositionManager, TradeRecord
logger = logging.getLogger(__name__)
class IncTrader:
"""
Incremental trader that manages a single strategy during backtesting.
This class handles:
- Strategy initialization and data feeding
- Trade decision logic based on strategy signals
- Risk management (stop loss, take profit)
- Performance tracking and metrics collection
The trader processes data points sequentially, feeding them to the strategy
and executing trades based on the generated signals.
Example:
from IncrementalTrader.strategies import MetaTrendStrategy
from IncrementalTrader.trader import IncTrader
strategy = MetaTrendStrategy("metatrend", params={"timeframe": "15min"})
trader = IncTrader(
strategy=strategy,
initial_usd=10000,
params={"stop_loss_pct": 0.02}
)
# Process data sequentially
for timestamp, ohlcv_data in data_stream:
trader.process_data_point(timestamp, ohlcv_data)
# Get results
results = trader.get_results()
"""
def __init__(self, strategy: IncStrategyBase, initial_usd: float = 10000,
params: Optional[Dict] = None):
"""
Initialize the incremental trader.
Args:
strategy: Incremental strategy instance
initial_usd: Initial USD balance
params: Trader parameters (stop_loss_pct, take_profit_pct, etc.)
"""
self.strategy = strategy
self.initial_usd = initial_usd
self.params = params or {}
# Initialize position manager
self.position_manager = PositionManager(initial_usd)
# Current state
self.current_timestamp = None
self.current_price = None
# Strategy state tracking
self.data_points_processed = 0
self.warmup_complete = False
# Risk management parameters
self.stop_loss_pct = self.params.get("stop_loss_pct", 0.0)
self.take_profit_pct = self.params.get("take_profit_pct", 0.0)
# Performance tracking
self.portfolio_history = []
logger.info(f"IncTrader initialized: strategy={strategy.name}, "
f"initial_usd=${initial_usd}, stop_loss={self.stop_loss_pct*100:.1f}%")
def process_data_point(self, timestamp: pd.Timestamp, ohlcv_data: Dict[str, float]) -> None:
"""
Process a single data point through the strategy and handle trading logic.
Args:
timestamp: Data point timestamp
ohlcv_data: OHLCV data dictionary with keys: open, high, low, close, volume
"""
self.current_timestamp = timestamp
self.current_price = ohlcv_data['close']
self.data_points_processed += 1
try:
# Feed data to strategy and get signal
signal = self.strategy.process_data_point(timestamp, ohlcv_data)
# Check if strategy is warmed up
if not self.warmup_complete and self.strategy.is_warmed_up:
self.warmup_complete = True
logger.info(f"Strategy {self.strategy.name} warmed up after "
f"{self.data_points_processed} data points")
# Only process signals if strategy is warmed up
if self.warmup_complete:
self._process_trading_logic(signal)
# Update performance tracking
self._update_performance_tracking()
except Exception as e:
logger.error(f"Error processing data point at {timestamp}: {e}")
raise
def _process_trading_logic(self, signal: Optional[IncStrategySignal]) -> None:
"""Process trading logic based on current position and strategy signals."""
if not self.position_manager.is_in_position():
# No position - check for entry signals
self._check_entry_signals(signal)
else:
# In position - check for exit signals
self._check_exit_signals(signal)
def _check_entry_signals(self, signal: Optional[IncStrategySignal]) -> None:
"""Check for entry signals when not in position."""
try:
# Check if we have a valid entry signal
if signal and signal.signal_type == "ENTRY" and signal.confidence > 0:
self._execute_entry(signal)
except Exception as e:
logger.error(f"Error checking entry signals: {e}")
def _check_exit_signals(self, signal: Optional[IncStrategySignal]) -> None:
"""Check for exit signals when in position."""
try:
# Check strategy exit signals first
if signal and signal.signal_type == "EXIT" and signal.confidence > 0:
exit_reason = signal.metadata.get("type", "STRATEGY_EXIT")
exit_price = signal.price if signal.price else self.current_price
self._execute_exit(exit_reason, exit_price)
return
# Check stop loss
if self.position_manager.check_stop_loss(self.current_price, self.stop_loss_pct):
self._execute_exit("STOP_LOSS", self.current_price)
return
# Check take profit
if self.position_manager.check_take_profit(self.current_price, self.take_profit_pct):
self._execute_exit("TAKE_PROFIT", self.current_price)
return
except Exception as e:
logger.error(f"Error checking exit signals: {e}")
def _execute_entry(self, signal: IncStrategySignal) -> None:
"""Execute entry trade."""
entry_price = signal.price if signal.price else self.current_price
try:
entry_details = self.position_manager.execute_entry(
entry_price, self.current_timestamp, self.strategy.name
)
logger.info(f"ENTRY: {self.strategy.name} at ${entry_price:.2f}, "
f"confidence={signal.confidence:.2f}, "
f"fee=${entry_details['entry_fee']:.2f}")
except Exception as e:
logger.error(f"Error executing entry: {e}")
raise
def _execute_exit(self, exit_reason: str, exit_price: Optional[float] = None) -> None:
"""Execute exit trade."""
exit_price = exit_price if exit_price else self.current_price
try:
exit_details = self.position_manager.execute_exit(
exit_price, self.current_timestamp, exit_reason, self.strategy.name
)
logger.info(f"EXIT: {self.strategy.name} at ${exit_price:.2f}, "
f"reason={exit_reason}, "
f"profit={exit_details['profit_pct']*100:.2f}%, "
f"fee=${exit_details['exit_fee']:.2f}")
except Exception as e:
logger.error(f"Error executing exit: {e}")
raise
def _update_performance_tracking(self) -> None:
"""Update performance tracking metrics."""
# Update position manager metrics
self.position_manager.update_performance_metrics(self.current_price)
# Track portfolio value over time
current_balance = self.position_manager.get_current_balance(self.current_price)
self.portfolio_history.append({
'timestamp': self.current_timestamp,
'balance': current_balance,
'price': self.current_price,
'position': self.position_manager.position
})
def finalize(self) -> None:
"""Finalize trading session (close any open positions)."""
if self.position_manager.is_in_position():
self._execute_exit("EOD", self.current_price)
logger.info(f"Closed final position for {self.strategy.name} at EOD")
def get_results(self) -> Dict[str, Any]:
"""
Get comprehensive trading results.
Returns:
Dict containing performance metrics, trade records, and statistics
"""
# Get performance summary from position manager
performance = self.position_manager.get_performance_summary()
# Get trades as dictionaries
trades = self.position_manager.get_trades_as_dicts()
# Build comprehensive results
results = {
"strategy_name": self.strategy.name,
"strategy_params": self.strategy.params,
"trader_params": self.params,
"data_points_processed": self.data_points_processed,
"warmup_complete": self.warmup_complete,
"trades": trades,
"portfolio_history": self.portfolio_history,
**performance # Include all performance metrics
}
# Add first and last trade info if available
if len(trades) > 0:
results["first_trade"] = {
"entry_time": trades[0]["entry_time"],
"entry": trades[0]["entry"]
}
results["last_trade"] = {
"exit_time": trades[-1]["exit_time"],
"exit": trades[-1]["exit"]
}
# Add final balance for compatibility
results["final_balance"] = performance["final_usd"]
return results
def get_current_state(self) -> Dict[str, Any]:
"""Get current trader state for debugging."""
position_state = self.position_manager.get_current_state()
return {
"strategy": self.strategy.name,
"current_price": self.current_price,
"current_timestamp": self.current_timestamp,
"data_points_processed": self.data_points_processed,
"warmup_complete": self.warmup_complete,
"strategy_state": self.strategy.get_current_state_summary(),
**position_state # Include all position state
}
def get_portfolio_value(self) -> float:
"""Get current portfolio value."""
return self.position_manager.get_current_balance(self.current_price)
def reset(self) -> None:
"""Reset trader to initial state."""
self.position_manager.reset()
self.strategy.reset_calculation_state()
self.current_timestamp = None
self.current_price = None
self.data_points_processed = 0
self.warmup_complete = False
self.portfolio_history.clear()
logger.info(f"IncTrader reset for strategy {self.strategy.name}")
def __repr__(self) -> str:
"""String representation of the trader."""
return (f"IncTrader(strategy={self.strategy.name}, "
f"position={self.position_manager.position}, "
f"balance=${self.position_manager.get_current_balance(self.current_price or 0):.2f}, "
f"trades={len(self.position_manager.trade_records)})")