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
9.9 KiB
Python

"""
Position Management for Incremental Trading
This module handles position state, balance tracking, and trade calculations
for the incremental trading system.
"""
import pandas as pd
import numpy as np
from typing import Dict, Optional, List, Any
from dataclasses import dataclass
import logging
logger = logging.getLogger(__name__)
@dataclass
class TradeRecord:
"""Record of a completed trade."""
entry_time: pd.Timestamp
exit_time: pd.Timestamp
entry_price: float
exit_price: float
entry_fee: float
exit_fee: float
profit_pct: float
exit_reason: str
strategy_name: str
class MarketFees:
"""Market fee calculations for different exchanges."""
@staticmethod
def calculate_okx_taker_maker_fee(amount: float, is_maker: bool = True) -> float:
"""Calculate OKX trading fees."""
fee_rate = 0.0008 if is_maker else 0.0010
return amount * fee_rate
@staticmethod
def calculate_binance_fee(amount: float, is_maker: bool = True) -> float:
"""Calculate Binance trading fees."""
fee_rate = 0.001 if is_maker else 0.001
return amount * fee_rate
class PositionManager:
"""
Manages trading position state and calculations.
This class handles:
- USD/coin balance tracking
- Position state management
- Trade execution calculations
- Fee calculations
- Performance metrics
"""
def __init__(self, initial_usd: float = 10000, fee_calculator=None):
"""
Initialize position manager.
Args:
initial_usd: Initial USD balance
fee_calculator: Fee calculation function (defaults to OKX)
"""
self.initial_usd = initial_usd
self.fee_calculator = fee_calculator or MarketFees.calculate_okx_taker_maker_fee
# Position state
self.usd = initial_usd
self.coin = 0.0
self.position = 0 # 0 = no position, 1 = long position
self.entry_price = 0.0
self.entry_time = None
# Performance tracking
self.max_balance = initial_usd
self.drawdowns = []
self.trade_records = []
logger.debug(f"PositionManager initialized with ${initial_usd}")
def is_in_position(self) -> bool:
"""Check if currently in a position."""
return self.position == 1
def get_current_balance(self, current_price: float) -> float:
"""Get current total balance value."""
if self.position == 0:
return self.usd
else:
return self.coin * current_price
def execute_entry(self, entry_price: float, timestamp: pd.Timestamp,
strategy_name: str) -> Dict[str, Any]:
"""
Execute entry trade.
Args:
entry_price: Entry price
timestamp: Entry timestamp
strategy_name: Name of the strategy
Returns:
Dict with entry details
"""
if self.position == 1:
raise ValueError("Cannot enter position: already in position")
# Calculate fees
entry_fee = self.fee_calculator(self.usd, is_maker=False)
usd_after_fee = self.usd - entry_fee
# Execute entry
self.coin = usd_after_fee / entry_price
self.entry_price = entry_price
self.entry_time = timestamp
self.usd = 0.0
self.position = 1
entry_details = {
'entry_price': entry_price,
'entry_time': timestamp,
'entry_fee': entry_fee,
'coin_amount': self.coin,
'strategy_name': strategy_name
}
logger.debug(f"ENTRY executed: ${entry_price:.2f}, fee=${entry_fee:.2f}")
return entry_details
def execute_exit(self, exit_price: float, timestamp: pd.Timestamp,
exit_reason: str, strategy_name: str) -> Dict[str, Any]:
"""
Execute exit trade.
Args:
exit_price: Exit price
timestamp: Exit timestamp
exit_reason: Reason for exit
strategy_name: Name of the strategy
Returns:
Dict with exit details and trade record
"""
if self.position == 0:
raise ValueError("Cannot exit position: not in position")
# Calculate exit
usd_gross = self.coin * exit_price
exit_fee = self.fee_calculator(usd_gross, is_maker=False)
self.usd = usd_gross - exit_fee
# Calculate profit
profit_pct = (exit_price - self.entry_price) / self.entry_price
# Calculate entry fee (for record keeping)
entry_fee = self.fee_calculator(self.coin * self.entry_price, is_maker=False)
# Create trade record
trade_record = TradeRecord(
entry_time=self.entry_time,
exit_time=timestamp,
entry_price=self.entry_price,
exit_price=exit_price,
entry_fee=entry_fee,
exit_fee=exit_fee,
profit_pct=profit_pct,
exit_reason=exit_reason,
strategy_name=strategy_name
)
self.trade_records.append(trade_record)
# Reset position
coin_amount = self.coin
self.coin = 0.0
self.position = 0
entry_price = self.entry_price
entry_time = self.entry_time
self.entry_price = 0.0
self.entry_time = None
exit_details = {
'exit_price': exit_price,
'exit_time': timestamp,
'exit_fee': exit_fee,
'profit_pct': profit_pct,
'exit_reason': exit_reason,
'trade_record': trade_record,
'final_usd': self.usd
}
logger.debug(f"EXIT executed: ${exit_price:.2f}, reason={exit_reason}, "
f"profit={profit_pct*100:.2f}%, fee=${exit_fee:.2f}")
return exit_details
def update_performance_metrics(self, current_price: float) -> None:
"""Update performance tracking metrics."""
current_balance = self.get_current_balance(current_price)
# Update max balance and drawdown
if current_balance > self.max_balance:
self.max_balance = current_balance
drawdown = (self.max_balance - current_balance) / self.max_balance
self.drawdowns.append(drawdown)
def check_stop_loss(self, current_price: float, stop_loss_pct: float) -> bool:
"""Check if stop loss should be triggered."""
if self.position == 0 or stop_loss_pct <= 0:
return False
stop_loss_price = self.entry_price * (1 - stop_loss_pct)
return current_price <= stop_loss_price
def check_take_profit(self, current_price: float, take_profit_pct: float) -> bool:
"""Check if take profit should be triggered."""
if self.position == 0 or take_profit_pct <= 0:
return False
take_profit_price = self.entry_price * (1 + take_profit_pct)
return current_price >= take_profit_price
def get_performance_summary(self) -> Dict[str, Any]:
"""Get performance summary statistics."""
final_balance = self.usd
n_trades = len(self.trade_records)
# Calculate statistics
if n_trades > 0:
profits = [trade.profit_pct for trade in self.trade_records]
wins = [p for p in profits if p > 0]
win_rate = len(wins) / n_trades
avg_trade = np.mean(profits)
total_fees = sum(trade.entry_fee + trade.exit_fee for trade in self.trade_records)
else:
win_rate = 0.0
avg_trade = 0.0
total_fees = 0.0
max_drawdown = max(self.drawdowns) if self.drawdowns else 0.0
profit_ratio = (final_balance - self.initial_usd) / self.initial_usd
return {
"initial_usd": self.initial_usd,
"final_usd": final_balance,
"profit_ratio": profit_ratio,
"n_trades": n_trades,
"win_rate": win_rate,
"max_drawdown": max_drawdown,
"avg_trade": avg_trade,
"total_fees_usd": total_fees
}
def get_trades_as_dicts(self) -> List[Dict[str, Any]]:
"""Convert trade records to dictionaries."""
trades = []
for trade in self.trade_records:
trades.append({
'entry_time': trade.entry_time,
'exit_time': trade.exit_time,
'entry': trade.entry_price,
'exit': trade.exit_price,
'profit_pct': trade.profit_pct,
'type': trade.exit_reason,
'fee_usd': trade.entry_fee + trade.exit_fee,
'strategy': trade.strategy_name
})
return trades
def get_current_state(self) -> Dict[str, Any]:
"""Get current position state."""
return {
"position": self.position,
"usd": self.usd,
"coin": self.coin,
"entry_price": self.entry_price,
"entry_time": self.entry_time,
"n_trades": len(self.trade_records),
"max_balance": self.max_balance
}
def reset(self) -> None:
"""Reset position manager to initial state."""
self.usd = self.initial_usd
self.coin = 0.0
self.position = 0
self.entry_price = 0.0
self.entry_time = None
self.max_balance = self.initial_usd
self.drawdowns.clear()
self.trade_records.clear()
logger.debug("PositionManager reset to initial state")
def __repr__(self) -> str:
"""String representation of position manager."""
return (f"PositionManager(position={self.position}, "
f"usd=${self.usd:.2f}, coin={self.coin:.6f}, "
f"trades={len(self.trade_records)})")