update
This commit is contained in:
893
vbt-topics/vbt-custom-metrics-analysis.md
Normal file
893
vbt-topics/vbt-custom-metrics-analysis.md
Normal file
@ -0,0 +1,893 @@
|
||||
Custom metrics in VectorBT PRO, which is a powerful feature for extending portfolio analysis beyond the built-in metrics.Now let me provide a comprehensive elaboration on custom metrics in VectorBT PRO:
|
||||
|
||||
# Custom Metrics in VectorBT PRO
|
||||
|
||||
Custom metrics in VectorBT PRO provide a powerful way to extend portfolio analysis beyond the built-in metrics. They allow you to calculate domain-specific metrics, implement proprietary performance measures, or create metrics tailored to your specific trading strategies.
|
||||
|
||||
## 1. Understanding the Metrics System
|
||||
|
||||
### Built-in Metrics Structure
|
||||
VectorBT PRO uses a configuration-based approach where metrics are stored in `Portfolio.metrics` as a `HybridConfig`:
|
||||
|
||||
```python
|
||||
# View all available metrics
|
||||
print(vbt.Portfolio.metrics)
|
||||
|
||||
# Get specific metric configuration
|
||||
print(vbt.Portfolio.metrics['sharpe_ratio'])
|
||||
```
|
||||
|
||||
### Metric Configuration Structure
|
||||
Each metric is defined as a dictionary with specific keys:
|
||||
|
||||
```python
|
||||
metric_config = {
|
||||
'title': 'My Custom Metric', # Display name
|
||||
'calc_func': calculation_function, # Function to calculate the metric
|
||||
'tags': ['custom', 'risk'], # Tags for filtering
|
||||
'apply_to_timedelta': False, # Whether to convert to timedelta
|
||||
'agg_func': None, # Aggregation function
|
||||
'resolve_calc_func': True, # Whether to resolve attributes
|
||||
# ... other configuration options
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Creating Custom Metrics
|
||||
|
||||
### Method 1: Simple Function-Based Metrics
|
||||
|
||||
```python
|
||||
# Add a simple custom metric
|
||||
vbt.Portfolio.metrics['total_bars'] = dict(
|
||||
title='Total Bars',
|
||||
calc_func=lambda self: len(self.wrapper.index)
|
||||
)
|
||||
|
||||
# Add skewness and kurtosis
|
||||
vbt.Portfolio.metrics['skew'] = dict(
|
||||
title='Skew',
|
||||
calc_func='returns.skew'
|
||||
)
|
||||
vbt.Portfolio.metrics['kurtosis'] = dict(
|
||||
title='Kurtosis',
|
||||
calc_func='returns.kurtosis'
|
||||
)
|
||||
```
|
||||
|
||||
### Method 2: Complex Custom Calculations
|
||||
|
||||
```python
|
||||
# Custom metric with multiple parameters
|
||||
def total_return_no_fees(self, orders):
|
||||
"""Calculate total return without fees"""
|
||||
return (self.total_profit + orders.fees.sum()) / self.get_init_cash() * 100
|
||||
|
||||
vbt.Portfolio.metrics['total_return_no_fees'] = dict(
|
||||
title='Total Return (No Fees) [%]',
|
||||
calc_func=total_return_no_fees,
|
||||
resolve_orders=True # Automatically resolve orders parameter
|
||||
)
|
||||
```
|
||||
|
||||
### Method 3: Using Lambda Functions with Settings
|
||||
|
||||
```python
|
||||
# PnL in dollar terms (for futures trading)
|
||||
vbt.Portfolio.metrics['pnl_dollars'] = dict(
|
||||
title='PnL ($)',
|
||||
calc_func=lambda self, settings: (self.value.iloc[-1] - self.value.iloc[0]) * 50,
|
||||
resolve_calc_func=False # Don't resolve attributes automatically
|
||||
)
|
||||
```
|
||||
|
||||
## 3. Advanced Custom Metrics
|
||||
|
||||
### Quantile-Based Metrics
|
||||
```python
|
||||
def value_at_risk_custom(returns, confidence_level=0.05):
|
||||
"""Custom VaR calculation"""
|
||||
return returns.quantile(confidence_level)
|
||||
|
||||
vbt.Portfolio.metrics['custom_var'] = dict(
|
||||
title='Custom VaR (5%)',
|
||||
calc_func=value_at_risk_custom,
|
||||
resolve_returns=True,
|
||||
confidence_level=0.05
|
||||
)
|
||||
```
|
||||
|
||||
### Multi-Component Metrics
|
||||
```python
|
||||
def comprehensive_trade_stats(trades):
|
||||
"""Return multiple trade statistics"""
|
||||
return {
|
||||
'long_trades': trades.direction_long.count(),
|
||||
'short_trades': trades.direction_short.count(),
|
||||
'long_pnl': trades.direction_long.pnl.sum(),
|
||||
'short_pnl': trades.direction_short.pnl.sum(),
|
||||
'avg_trade_duration': trades.duration.mean()
|
||||
}
|
||||
|
||||
vbt.Portfolio.metrics['trade_breakdown'] = dict(
|
||||
title='Trade Breakdown',
|
||||
calc_func=comprehensive_trade_stats,
|
||||
resolve_trades=True
|
||||
)
|
||||
```
|
||||
|
||||
### Time-Based Metrics
|
||||
```python
|
||||
def monthly_returns_volatility(returns):
|
||||
"""Calculate monthly returns volatility"""
|
||||
monthly_returns = returns.resample('M').sum()
|
||||
return monthly_returns.std() * np.sqrt(12)
|
||||
|
||||
vbt.Portfolio.metrics['monthly_vol'] = dict(
|
||||
title='Monthly Volatility',
|
||||
calc_func=monthly_returns_volatility,
|
||||
resolve_returns=True
|
||||
)
|
||||
```
|
||||
|
||||
## 4. Metric Resolution and Parameters
|
||||
|
||||
### Automatic Parameter Resolution
|
||||
VectorBT PRO can automatically resolve portfolio attributes as parameters:
|
||||
|
||||
```python
|
||||
# These parameters will be automatically resolved:
|
||||
vbt.Portfolio.metrics['custom_metric'] = dict(
|
||||
title='Custom Metric',
|
||||
calc_func=lambda returns, trades, orders: calculation_logic(returns, trades, orders),
|
||||
resolve_returns=True, # Passes self.returns
|
||||
resolve_trades=True, # Passes self.trades
|
||||
resolve_orders=True # Passes self.orders
|
||||
)
|
||||
```
|
||||
|
||||
### Common Resolvable Parameters
|
||||
- `self` - The portfolio instance
|
||||
- `returns` - Portfolio returns
|
||||
- `trades` - Trade records
|
||||
- `orders` - Order records
|
||||
- `drawdowns` - Drawdown records
|
||||
- `value` - Portfolio value
|
||||
- `close` - Close prices
|
||||
- `init_cash` - Initial cash
|
||||
- `total_profit` - Total profit
|
||||
- `wrapper` - Array wrapper (for index/column info)
|
||||
|
||||
## 5. Global vs Instance-Level Metrics
|
||||
|
||||
### Global Metrics (Class-Level)
|
||||
```python
|
||||
# Add to all future Portfolio instances
|
||||
vbt.Portfolio.metrics['my_metric'] = metric_config
|
||||
|
||||
# Or modify settings globally
|
||||
vbt.settings.portfolio['stats']['metrics'] = list(vbt.Portfolio.metrics.items()) + [
|
||||
('my_metric', metric_config)
|
||||
]
|
||||
```
|
||||
|
||||
### Instance-Level Metrics
|
||||
```python
|
||||
# Add to specific portfolio instance
|
||||
pf._metrics['my_metric'] = metric_config
|
||||
|
||||
# Then use it
|
||||
pf.stats(['my_metric'])
|
||||
```
|
||||
|
||||
## 6. Using Custom Metrics
|
||||
|
||||
### Basic Usage
|
||||
```python
|
||||
# Calculate specific custom metrics
|
||||
pf.stats(['total_bars', 'skew', 'kurtosis'])
|
||||
|
||||
# Calculate all metrics including custom ones
|
||||
pf.stats('all')
|
||||
|
||||
# Filter by tags
|
||||
pf.stats(tags=['custom'])
|
||||
```
|
||||
|
||||
### Advanced Usage with Settings
|
||||
```python
|
||||
# Use custom metrics in optimization
|
||||
results = []
|
||||
for param in parameter_combinations:
|
||||
pf = vbt.Portfolio.from_signals(close, entries, exits, **param)
|
||||
stats = pf.stats(['total_return', 'sharpe_ratio', 'my_custom_metric'])
|
||||
results.append(stats)
|
||||
|
||||
# Create comparison DataFrame
|
||||
comparison_df = pd.DataFrame(results)
|
||||
```
|
||||
|
||||
## 7. Real-World Examples
|
||||
|
||||
### Futures Trading Metrics
|
||||
```python
|
||||
# Point-based P&L for futures
|
||||
vbt.Portfolio.metrics['pnl_points'] = dict(
|
||||
title='P&L (Points)',
|
||||
calc_func=lambda self: (self.value.iloc[-1] - self.value.iloc[0]) / self.close.iloc[0] * 10000
|
||||
)
|
||||
|
||||
# Risk-adjusted return for futures
|
||||
vbt.Portfolio.metrics['risk_adjusted_return'] = dict(
|
||||
title='Risk Adjusted Return',
|
||||
calc_func=lambda self, returns: self.total_return / returns.std() * np.sqrt(252),
|
||||
resolve_returns=True
|
||||
)
|
||||
```
|
||||
|
||||
### Intraday Strategy Metrics
|
||||
```python
|
||||
# Time-of-day analysis
|
||||
def intraday_performance(orders):
|
||||
"""Analyze performance by hour of day"""
|
||||
order_df = orders.records_readable
|
||||
order_df['hour'] = order_df.index.hour
|
||||
return order_df.groupby('hour')['PnL'].mean()
|
||||
|
||||
vbt.Portfolio.metrics['hourly_performance'] = dict(
|
||||
title='Hourly Performance',
|
||||
calc_func=intraday_performance,
|
||||
resolve_orders=True
|
||||
)
|
||||
```
|
||||
|
||||
### Market Regime Metrics
|
||||
```python
|
||||
def regime_performance(returns, benchmark_returns):
|
||||
"""Performance in different market regimes"""
|
||||
bull_mask = benchmark_returns > benchmark_returns.quantile(0.6)
|
||||
bear_mask = benchmark_returns < benchmark_returns.quantile(0.4)
|
||||
|
||||
return {
|
||||
'bull_return': returns[bull_mask].mean(),
|
||||
'bear_return': returns[bear_mask].mean(),
|
||||
'bull_sharpe': returns[bull_mask].mean() / returns[bull_mask].std() * np.sqrt(252),
|
||||
'bear_sharpe': returns[bear_mask].mean() / returns[bear_mask].std() * np.sqrt(252)
|
||||
}
|
||||
|
||||
vbt.Portfolio.metrics['regime_analysis'] = dict(
|
||||
title='Market Regime Analysis',
|
||||
calc_func=regime_performance,
|
||||
resolve_returns=True,
|
||||
resolve_bm_returns=True
|
||||
)
|
||||
```
|
||||
|
||||
## 8. Best Practices
|
||||
|
||||
### 1. Naming Conventions
|
||||
- Use descriptive names: `monthly_volatility` instead of `mv`
|
||||
- Include units in title: `'Max Drawdown [%]'`
|
||||
- Use consistent naming patterns
|
||||
|
||||
### 2. Error Handling
|
||||
```python
|
||||
def robust_metric(returns):
|
||||
"""Metric with error handling"""
|
||||
try:
|
||||
if len(returns) < 2:
|
||||
return np.nan
|
||||
return returns.std() * np.sqrt(252)
|
||||
except Exception as e:
|
||||
print(f"Error calculating metric: {e}")
|
||||
return np.nan
|
||||
```
|
||||
|
||||
### 3. Performance Optimization
|
||||
```python
|
||||
# Use vectorized operations
|
||||
def efficient_metric(returns):
|
||||
"""Efficient vectorized calculation"""
|
||||
return returns.rolling(30).std().mean()
|
||||
|
||||
# Avoid loops when possible
|
||||
def inefficient_metric(returns):
|
||||
"""Avoid this approach"""
|
||||
results = []
|
||||
for i in range(len(returns)):
|
||||
results.append(some_calculation(returns.iloc[i]))
|
||||
return np.mean(results)
|
||||
```
|
||||
|
||||
### 4. Documentation
|
||||
```python
|
||||
vbt.Portfolio.metrics['documented_metric'] = dict(
|
||||
title='Well Documented Metric',
|
||||
calc_func=lambda returns: returns.std() * np.sqrt(252),
|
||||
resolve_returns=True,
|
||||
tags=['custom', 'risk', 'volatility'],
|
||||
# Add description in comments or docstrings
|
||||
)
|
||||
```
|
||||
|
||||
## 9. Common Pitfalls and Solutions
|
||||
|
||||
### Pitfall 1: Metric Not Available After Creation
|
||||
```python
|
||||
# ❌ Wrong: Metric added after portfolio creation
|
||||
pf = vbt.Portfolio.from_signals(...)
|
||||
vbt.Portfolio.metrics['my_metric'] = metric_config
|
||||
pf.stats(['my_metric']) # KeyError!
|
||||
|
||||
# ✅ Correct: Add metric before portfolio creation
|
||||
vbt.Portfolio.metrics['my_metric'] = metric_config
|
||||
pf = vbt.Portfolio.from_signals(...)
|
||||
pf.stats(['my_metric']) # Works!
|
||||
```
|
||||
|
||||
### Pitfall 2: Incorrect Parameter Resolution
|
||||
```python
|
||||
# ❌ Wrong: Using external variables
|
||||
portfolio_instance = some_portfolio
|
||||
vbt.Portfolio.metrics['bad_metric'] = dict(
|
||||
calc_func=lambda self: portfolio_instance.total_return # External reference
|
||||
)
|
||||
|
||||
# ✅ Correct: Using self parameter
|
||||
vbt.Portfolio.metrics['good_metric'] = dict(
|
||||
calc_func=lambda self: self.total_return # Self reference
|
||||
)
|
||||
```
|
||||
|
||||
### Pitfall 3: Missing Error Handling
|
||||
```python
|
||||
# ❌ Wrong: No error handling
|
||||
def risky_metric(trades):
|
||||
return trades.pnl.sum() / trades.duration.mean() # Division by zero possible
|
||||
|
||||
# ✅ Correct: With error handling
|
||||
def safe_metric(trades):
|
||||
if len(trades) == 0 or trades.duration.mean() == 0:
|
||||
return np.nan
|
||||
return trades.pnl.sum() / trades.duration.mean()
|
||||
```
|
||||
|
||||
Custom metrics in VectorBT PRO provide unlimited flexibility to analyze your trading strategies exactly how you need. They integrate seamlessly with the existing stats system and can be used in optimization, comparison, and reporting workflows.
|
||||
|
||||
I'll provide a comprehensive analysis of VectorBT PRO's `pf.trades` analysis capabilities, with a focus on the specific metrics you mentioned.# Comprehensive VectorBT PRO `pf.trades` Analysis
|
||||
|
||||
The `pf.trades` object in VectorBT PRO provides extensive capabilities for analyzing trading performance. Here's a comprehensive guide focusing on directional analysis, temporal patterns, and advanced trade analytics.
|
||||
|
||||
## 1. Basic Trade Analysis
|
||||
|
||||
### Trade Counts by Direction
|
||||
```python
|
||||
# Basic trade counts
|
||||
total_trades = pf.trades.count()
|
||||
long_trades = pf.trades.direction_long.count()
|
||||
short_trades = pf.trades.direction_short.count()
|
||||
|
||||
print(f"Total trades: {total_trades}")
|
||||
print(f"Long trades: {long_trades}")
|
||||
print(f"Short trades: {short_trades}")
|
||||
|
||||
# Alternative using records
|
||||
trade_records = pf.trades.records_readable
|
||||
direction_counts = trade_records['Direction'].value_counts()
|
||||
print(f"\nDirection breakdown:\n{direction_counts}")
|
||||
```
|
||||
|
||||
### P&L Analysis by Direction
|
||||
```python
|
||||
# Total P&L by direction
|
||||
long_pnl = pf.trades.direction_long.pnl.sum()
|
||||
short_pnl = pf.trades.direction_short.pnl.sum()
|
||||
total_pnl = pf.trades.pnl.sum()
|
||||
|
||||
print(f"Long P&L: {long_pnl:.2f}")
|
||||
print(f"Short P&L: {short_pnl:.2f}")
|
||||
print(f"Total P&L: {total_pnl:.2f}")
|
||||
|
||||
# P&L statistics by direction
|
||||
long_stats = pf.trades.direction_long.pnl.describe()
|
||||
short_stats = pf.trades.direction_short.pnl.describe()
|
||||
```
|
||||
|
||||
## 2. Daily P&L Analysis
|
||||
|
||||
### Daily P&L Calculation
|
||||
```python
|
||||
# Method 1: Using trade records with date grouping
|
||||
trade_records = pf.trades.records_readable
|
||||
trade_records['exit_date'] = trade_records.index.date
|
||||
|
||||
# Daily P&L overall
|
||||
daily_pnl = trade_records.groupby('exit_date')['PnL'].sum()
|
||||
|
||||
# Daily P&L by direction
|
||||
daily_pnl_by_direction = trade_records.groupby(['exit_date', 'Direction'])['PnL'].sum().unstack(fill_value=0)
|
||||
|
||||
print("Daily P&L by Direction:")
|
||||
print(daily_pnl_by_direction.head())
|
||||
```
|
||||
|
||||
### Daily P&L for Each Direction
|
||||
```python
|
||||
# Separate long and short daily P&L
|
||||
long_trades_records = trade_records[trade_records['Direction'] == 'Long']
|
||||
short_trades_records = trade_records[trade_records['Direction'] == 'Short']
|
||||
|
||||
daily_long_pnl = long_trades_records.groupby('exit_date')['PnL'].sum()
|
||||
daily_short_pnl = short_trades_records.groupby('exit_date')['PnL'].sum()
|
||||
|
||||
# Combine into comprehensive daily analysis
|
||||
daily_analysis = pd.DataFrame({
|
||||
'Total_PnL': daily_pnl,
|
||||
'Long_PnL': daily_long_pnl,
|
||||
'Short_PnL': daily_short_pnl,
|
||||
'Long_Trades': long_trades_records.groupby('exit_date').size(),
|
||||
'Short_Trades': short_trades_records.groupby('exit_date').size()
|
||||
}).fillna(0)
|
||||
|
||||
print("Daily Trade Analysis:")
|
||||
print(daily_analysis.head())
|
||||
```
|
||||
|
||||
## 3. Hourly P&L Analysis by Exit Time
|
||||
|
||||
### Hourly P&L by Direction
|
||||
```python
|
||||
# Extract hour from exit time
|
||||
trade_records = pf.trades.records_readable
|
||||
trade_records['exit_hour'] = trade_records.index.hour
|
||||
|
||||
# Hourly P&L analysis
|
||||
hourly_pnl_analysis = trade_records.groupby(['exit_hour', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count'],
|
||||
'Return': ['mean', 'std']
|
||||
}).round(4)
|
||||
|
||||
print("Hourly P&L Analysis by Direction:")
|
||||
print(hourly_pnl_analysis)
|
||||
|
||||
# Separate analysis for each direction
|
||||
hourly_long_pnl = trade_records[trade_records['Direction'] == 'Long'].groupby('exit_hour')['PnL'].agg(['sum', 'mean', 'count'])
|
||||
hourly_short_pnl = trade_records[trade_records['Direction'] == 'Short'].groupby('exit_hour')['PnL'].agg(['sum', 'mean', 'count'])
|
||||
|
||||
print("\nHourly Long P&L:")
|
||||
print(hourly_long_pnl)
|
||||
|
||||
print("\nHourly Short P&L:")
|
||||
print(hourly_short_pnl)
|
||||
```
|
||||
|
||||
### Advanced Hourly Analysis
|
||||
```python
|
||||
# Create comprehensive hourly performance matrix
|
||||
def hourly_performance_analysis(trades_records):
|
||||
"""Comprehensive hourly performance analysis"""
|
||||
|
||||
# Add time components
|
||||
trades_records['exit_hour'] = trades_records.index.hour
|
||||
trades_records['entry_hour'] = pd.to_datetime(trades_records['Entry Index']).dt.hour
|
||||
|
||||
# Hourly exit analysis
|
||||
hourly_stats = trades_records.groupby(['exit_hour', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count', 'std'],
|
||||
'Return': ['mean', 'std'],
|
||||
'Size': 'mean'
|
||||
}).round(4)
|
||||
|
||||
return hourly_stats
|
||||
|
||||
hourly_performance = hourly_performance_analysis(trade_records)
|
||||
```
|
||||
|
||||
## 4. Day of Week Analysis
|
||||
|
||||
### P&L by Day of Week and Direction
|
||||
```python
|
||||
# Add day of week analysis
|
||||
trade_records['exit_day_of_week'] = trade_records.index.day_name()
|
||||
trade_records['exit_weekday'] = trade_records.index.weekday # 0=Monday, 6=Sunday
|
||||
|
||||
# Day of week P&L analysis
|
||||
dow_analysis = trade_records.groupby(['exit_day_of_week', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count'],
|
||||
'Return': ['mean', 'std'],
|
||||
'Size': 'mean'
|
||||
}).round(4)
|
||||
|
||||
print("Day of Week Analysis:")
|
||||
print(dow_analysis)
|
||||
|
||||
# Pivot for easier viewing
|
||||
dow_pivot = trade_records.pivot_table(
|
||||
index='exit_day_of_week',
|
||||
columns='Direction',
|
||||
values='PnL',
|
||||
aggfunc=['sum', 'mean', 'count'],
|
||||
fill_value=0
|
||||
)
|
||||
|
||||
print("\nDay of Week Pivot Analysis:")
|
||||
print(dow_pivot)
|
||||
```
|
||||
|
||||
### Advanced Day of Week Patterns
|
||||
```python
|
||||
# Create comprehensive day of week analysis
|
||||
def day_of_week_analysis(trades_records):
|
||||
"""Comprehensive day of week performance analysis"""
|
||||
|
||||
# Ensure we have day names in proper order
|
||||
day_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||
|
||||
trades_records['exit_day_name'] = trades_records.index.day_name()
|
||||
|
||||
# Group by day and direction
|
||||
dow_stats = trades_records.groupby(['exit_day_name', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count', 'std'],
|
||||
'Return': ['mean', 'std'],
|
||||
'Size': 'mean',
|
||||
'Entry Fees': 'mean',
|
||||
'Exit Fees': 'mean'
|
||||
}).round(4)
|
||||
|
||||
# Reorder by day
|
||||
dow_stats = dow_stats.reindex(day_order, level=0)
|
||||
|
||||
return dow_stats
|
||||
|
||||
dow_comprehensive = day_of_week_analysis(trade_records)
|
||||
```
|
||||
|
||||
## 5. Advanced Temporal Analysis
|
||||
|
||||
### Combined Time Pattern Analysis
|
||||
```python
|
||||
# Create comprehensive time pattern analysis
|
||||
def comprehensive_time_analysis(pf):
|
||||
"""Complete temporal analysis of trades"""
|
||||
|
||||
trades_records = pf.trades.records_readable
|
||||
|
||||
# Add all time components
|
||||
trades_records['exit_hour'] = trades_records.index.hour
|
||||
trades_records['exit_day_name'] = trades_records.index.day_name()
|
||||
trades_records['exit_month'] = trades_records.index.month
|
||||
trades_records['exit_date'] = trades_records.index.date
|
||||
|
||||
# 1. Hourly analysis
|
||||
hourly_stats = trades_records.groupby(['exit_hour', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count'],
|
||||
'Return': ['mean', 'std']
|
||||
}).round(4)
|
||||
|
||||
# 2. Daily analysis
|
||||
daily_stats = trades_records.groupby(['exit_day_name', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count'],
|
||||
'Return': ['mean', 'std']
|
||||
}).round(4)
|
||||
|
||||
# 3. Monthly analysis
|
||||
monthly_stats = trades_records.groupby(['exit_month', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count'],
|
||||
'Return': ['mean', 'std']
|
||||
}).round(4)
|
||||
|
||||
# 4. Combined hour-day analysis
|
||||
hour_day_stats = trades_records.groupby(['exit_day_name', 'exit_hour', 'Direction']).agg({
|
||||
'PnL': ['sum', 'mean', 'count']
|
||||
}).round(4)
|
||||
|
||||
return {
|
||||
'hourly': hourly_stats,
|
||||
'daily': daily_stats,
|
||||
'monthly': monthly_stats,
|
||||
'hour_day': hour_day_stats
|
||||
}
|
||||
|
||||
# Execute comprehensive analysis
|
||||
time_analysis = comprehensive_time_analysis(pf)
|
||||
|
||||
# Display results
|
||||
print("=== HOURLY ANALYSIS ===")
|
||||
print(time_analysis['hourly'])
|
||||
print("\n=== DAILY ANALYSIS ===")
|
||||
print(time_analysis['daily'])
|
||||
print("\n=== MONTHLY ANALYSIS ===")
|
||||
print(time_analysis['monthly'])
|
||||
```
|
||||
|
||||
## 6. Custom Metrics for Trade Analysis
|
||||
|
||||
### Custom Trade Metrics
|
||||
```python
|
||||
# Add custom metrics to Portfolio for directional analysis
|
||||
vbt.Portfolio.metrics['long_trade_count'] = dict(
|
||||
title='Long Trade Count',
|
||||
calc_func=lambda trades: trades.direction_long.count(),
|
||||
resolve_trades=True
|
||||
)
|
||||
|
||||
vbt.Portfolio.metrics['short_trade_count'] = dict(
|
||||
title='Short Trade Count',
|
||||
calc_func=lambda trades: trades.direction_short.count(),
|
||||
resolve_trades=True
|
||||
)
|
||||
|
||||
vbt.Portfolio.metrics['long_pnl_total'] = dict(
|
||||
title='Long P&L Total',
|
||||
calc_func=lambda trades: trades.direction_long.pnl.sum(),
|
||||
resolve_trades=True
|
||||
)
|
||||
|
||||
vbt.Portfolio.metrics['short_pnl_total'] = dict(
|
||||
title='Short P&L Total',
|
||||
calc_func=lambda trades: trades.direction_short.pnl.sum(),
|
||||
resolve_trades=True
|
||||
)
|
||||
|
||||
# Temporal metrics
|
||||
vbt.Portfolio.metrics['best_hour_pnl'] = dict(
|
||||
title='Best Hour P&L',
|
||||
calc_func=lambda trades: trades.records_readable.groupby(trades.records_readable.index.hour)['PnL'].sum().max(),
|
||||
resolve_trades=True
|
||||
)
|
||||
|
||||
vbt.Portfolio.metrics['worst_hour_pnl'] = dict(
|
||||
title='Worst Hour P&L',
|
||||
calc_func=lambda trades: trades.records_readable.groupby(trades.records_readable.index.hour)['PnL'].sum().min(),
|
||||
resolve_trades=True
|
||||
)
|
||||
```
|
||||
|
||||
## 7. Performance Analysis Functions
|
||||
|
||||
### Comprehensive Trade Performance Function
|
||||
```python
|
||||
def analyze_trade_performance(pf):
|
||||
"""Comprehensive trade performance analysis"""
|
||||
|
||||
trades = pf.trades
|
||||
records = trades.records_readable
|
||||
|
||||
# Basic directional statistics
|
||||
direction_stats = {
|
||||
'Long': {
|
||||
'count': trades.direction_long.count(),
|
||||
'total_pnl': trades.direction_long.pnl.sum(),
|
||||
'avg_pnl': trades.direction_long.pnl.mean(),
|
||||
'win_rate': trades.direction_long.win_rate,
|
||||
'profit_factor': trades.direction_long.profit_factor
|
||||
},
|
||||
'Short': {
|
||||
'count': trades.direction_short.count(),
|
||||
'total_pnl': trades.direction_short.pnl.sum(),
|
||||
'avg_pnl': trades.direction_short.pnl.mean(),
|
||||
'win_rate': trades.direction_short.win_rate,
|
||||
'profit_factor': trades.direction_short.profit_factor
|
||||
}
|
||||
}
|
||||
|
||||
# Temporal analysis
|
||||
records['exit_hour'] = records.index.hour
|
||||
records['exit_day'] = records.index.day_name()
|
||||
records['exit_date'] = records.index.date
|
||||
|
||||
# Hourly P&L by direction
|
||||
hourly_pnl = records.groupby(['exit_hour', 'Direction'])['PnL'].agg(['sum', 'mean', 'count'])
|
||||
|
||||
# Daily P&L by direction
|
||||
daily_pnl = records.groupby(['exit_day', 'Direction'])['PnL'].agg(['sum', 'mean', 'count'])
|
||||
|
||||
# Date-based P&L
|
||||
date_pnl = records.groupby(['exit_date', 'Direction'])['PnL'].agg(['sum', 'mean', 'count'])
|
||||
|
||||
# Best/worst performing times
|
||||
best_hours = records.groupby(['exit_hour', 'Direction'])['PnL'].sum().groupby('Direction').idxmax()
|
||||
worst_hours = records.groupby(['exit_hour', 'Direction'])['PnL'].sum().groupby('Direction').idxmin()
|
||||
|
||||
return {
|
||||
'direction_stats': direction_stats,
|
||||
'hourly_pnl': hourly_pnl,
|
||||
'daily_pnl': daily_pnl,
|
||||
'date_pnl': date_pnl,
|
||||
'best_hours': best_hours,
|
||||
'worst_hours': worst_hours
|
||||
}
|
||||
|
||||
# Execute analysis
|
||||
performance_analysis = analyze_trade_performance(pf)
|
||||
```
|
||||
|
||||
## 8. Visualization Functions
|
||||
|
||||
### Trade Performance Visualization
|
||||
```python
|
||||
def plot_trade_performance(pf):
|
||||
"""Create comprehensive trade performance plots"""
|
||||
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
records = pf.trades.records_readable
|
||||
records['exit_hour'] = records.index.hour
|
||||
records['exit_day'] = records.index.day_name()
|
||||
|
||||
# Create subplots
|
||||
fig = make_subplots(
|
||||
rows=2, cols=2,
|
||||
subplot_titles=['Hourly P&L by Direction', 'Daily P&L by Direction',
|
||||
'P&L Distribution', 'Cumulative P&L by Direction'],
|
||||
specs=[[{"secondary_y": True}, {"secondary_y": True}],
|
||||
[{"secondary_y": False}, {"secondary_y": False}]]
|
||||
)
|
||||
|
||||
# Hourly P&L
|
||||
hourly_long = records[records['Direction'] == 'Long'].groupby('exit_hour')['PnL'].sum()
|
||||
hourly_short = records[records['Direction'] == 'Short'].groupby('exit_hour')['PnL'].sum()
|
||||
|
||||
fig.add_trace(go.Bar(x=hourly_long.index, y=hourly_long.values, name='Long Hourly', marker_color='green'), row=1, col=1)
|
||||
fig.add_trace(go.Bar(x=hourly_short.index, y=hourly_short.values, name='Short Hourly', marker_color='red'), row=1, col=1)
|
||||
|
||||
# Daily P&L
|
||||
day_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||
daily_long = records[records['Direction'] == 'Long'].groupby('exit_day')['PnL'].sum().reindex(day_order, fill_value=0)
|
||||
daily_short = records[records['Direction'] == 'Short'].groupby('exit_day')['PnL'].sum().reindex(day_order, fill_value=0)
|
||||
|
||||
fig.add_trace(go.Bar(x=daily_long.index, y=daily_long.values, name='Long Daily', marker_color='lightgreen'), row=1, col=2)
|
||||
fig.add_trace(go.Bar(x=daily_short.index, y=daily_short.values, name='Short Daily', marker_color='lightcoral'), row=1, col=2)
|
||||
|
||||
# P&L Distribution
|
||||
fig.add_trace(go.Histogram(x=records[records['Direction'] == 'Long']['PnL'], name='Long Distribution', opacity=0.7), row=2, col=1)
|
||||
fig.add_trace(go.Histogram(x=records[records['Direction'] == 'Short']['PnL'], name='Short Distribution', opacity=0.7), row=2, col=1)
|
||||
|
||||
# Cumulative P&L
|
||||
long_cumulative = records[records['Direction'] == 'Long']['PnL'].cumsum()
|
||||
short_cumulative = records[records['Direction'] == 'Short']['PnL'].cumsum()
|
||||
|
||||
fig.add_trace(go.Scatter(y=long_cumulative.values, mode='lines', name='Long Cumulative', line=dict(color='green')), row=2, col=2)
|
||||
fig.add_trace(go.Scatter(y=short_cumulative.values, mode='lines', name='Short Cumulative', line=dict(color='red')), row=2, col=2)
|
||||
|
||||
fig.update_layout(height=800, title_text="Comprehensive Trade Analysis")
|
||||
return fig
|
||||
|
||||
# Create visualization
|
||||
# trade_plot = plot_trade_performance(pf)
|
||||
# trade_plot.show()
|
||||
```
|
||||
|
||||
## 9. Advanced Analytics
|
||||
|
||||
### Trade Streaks and Patterns
|
||||
```python
|
||||
def analyze_trade_patterns(pf):
|
||||
"""Analyze trade patterns and streaks"""
|
||||
|
||||
trades = pf.trades
|
||||
records = trades.records_readable
|
||||
|
||||
# Winning and losing streaks
|
||||
winning_streaks = trades.winning_streak.records_readable
|
||||
losing_streaks = trades.losing_streak.records_readable
|
||||
|
||||
# Pattern analysis
|
||||
patterns = {
|
||||
'longest_winning_streak': winning_streaks['Duration'].max() if len(winning_streaks) > 0 else 0,
|
||||
'longest_losing_streak': losing_streaks['Duration'].max() if len(losing_streaks) > 0 else 0,
|
||||
'avg_winning_streak': winning_streaks['Duration'].mean() if len(winning_streaks) > 0 else 0,
|
||||
'avg_losing_streak': losing_streaks['Duration'].mean() if len(losing_streaks) > 0 else 0,
|
||||
}
|
||||
|
||||
# Direction-specific patterns
|
||||
long_patterns = analyze_direction_patterns(trades.direction_long)
|
||||
short_patterns = analyze_direction_patterns(trades.direction_short)
|
||||
|
||||
return {
|
||||
'overall_patterns': patterns,
|
||||
'long_patterns': long_patterns,
|
||||
'short_patterns': short_patterns
|
||||
}
|
||||
|
||||
def analyze_direction_patterns(direction_trades):
|
||||
"""Analyze patterns for specific direction"""
|
||||
|
||||
if direction_trades.count() == 0:
|
||||
return {}
|
||||
|
||||
return {
|
||||
'total_trades': direction_trades.count(),
|
||||
'win_rate': direction_trades.win_rate,
|
||||
'profit_factor': direction_trades.profit_factor,
|
||||
'avg_winner': direction_trades.winning.pnl.mean() if direction_trades.winning.count() > 0 else 0,
|
||||
'avg_loser': direction_trades.losing.pnl.mean() if direction_trades.losing.count() > 0 else 0,
|
||||
'largest_winner': direction_trades.pnl.max(),
|
||||
'largest_loser': direction_trades.pnl.min(),
|
||||
'total_pnl': direction_trades.pnl.sum()
|
||||
}
|
||||
|
||||
# Execute pattern analysis
|
||||
pattern_analysis = analyze_trade_patterns(pf)
|
||||
```
|
||||
|
||||
## 10. Summary Report Function
|
||||
|
||||
### Comprehensive Trade Report
|
||||
```python
|
||||
def generate_trade_report(pf):
|
||||
"""Generate comprehensive trade analysis report"""
|
||||
|
||||
print("="*80)
|
||||
print("COMPREHENSIVE TRADE ANALYSIS REPORT")
|
||||
print("="*80)
|
||||
|
||||
# Basic Statistics
|
||||
trades = pf.trades
|
||||
total_trades = trades.count()
|
||||
long_trades = trades.direction_long.count()
|
||||
short_trades = trades.direction_short.count()
|
||||
|
||||
print(f"\n📊 BASIC STATISTICS")
|
||||
print(f"Total Trades: {total_trades}")
|
||||
print(f"Long Trades: {long_trades} ({long_trades/total_trades*100:.1f}%)")
|
||||
print(f"Short Trades: {short_trades} ({short_trades/total_trades*100:.1f}%)")
|
||||
|
||||
# P&L Analysis
|
||||
print(f"\n💰 P&L ANALYSIS")
|
||||
print(f"Total P&L: ${trades.pnl.sum():.2f}")
|
||||
print(f"Long P&L: ${trades.direction_long.pnl.sum():.2f}")
|
||||
print(f"Short P&L: ${trades.direction_short.pnl.sum():.2f}")
|
||||
print(f"Average P&L per Trade: ${trades.pnl.mean():.2f}")
|
||||
|
||||
# Temporal Analysis
|
||||
records = trades.records_readable
|
||||
records['exit_hour'] = records.index.hour
|
||||
records['exit_day'] = records.index.day_name()
|
||||
|
||||
print(f"\n⏰ TEMPORAL ANALYSIS")
|
||||
|
||||
# Best/Worst Hours
|
||||
hourly_pnl = records.groupby('exit_hour')['PnL'].sum()
|
||||
best_hour = hourly_pnl.idxmax()
|
||||
worst_hour = hourly_pnl.idxmin()
|
||||
|
||||
print(f"Best Hour: {best_hour}:00 (${hourly_pnl[best_hour]:.2f})")
|
||||
print(f"Worst Hour: {worst_hour}:00 (${hourly_pnl[worst_hour]:.2f})")
|
||||
|
||||
# Best/Worst Days
|
||||
daily_pnl = records.groupby('exit_day')['PnL'].sum()
|
||||
best_day = daily_pnl.idxmax()
|
||||
worst_day = daily_pnl.idxmin()
|
||||
|
||||
print(f"Best Day: {best_day} (${daily_pnl[best_day]:.2f})")
|
||||
print(f"Worst Day: {worst_day} (${daily_pnl[worst_day]:.2f})")
|
||||
|
||||
# Direction Performance
|
||||
print(f"\n📈 DIRECTION PERFORMANCE")
|
||||
if long_trades > 0:
|
||||
print(f"Long Win Rate: {trades.direction_long.win_rate:.2%}")
|
||||
print(f"Long Profit Factor: {trades.direction_long.profit_factor:.2f}")
|
||||
|
||||
if short_trades > 0:
|
||||
print(f"Short Win Rate: {trades.direction_short.win_rate:.2%}")
|
||||
print(f"Short Profit Factor: {trades.direction_short.profit_factor:.2f}")
|
||||
|
||||
print("="*80)
|
||||
|
||||
# Generate report
|
||||
generate_trade_report(pf)
|
||||
```
|
||||
|
||||
This comprehensive analysis framework provides all the tools needed to analyze `pf.trades` with particular focus on:
|
||||
|
||||
1. **Direction-specific analysis** - Separate analysis for long and short trades
|
||||
2. **Daily P&L patterns** - Understanding daily performance patterns
|
||||
3. **Hourly P&L by direction** - Identifying optimal trading hours for each direction
|
||||
4. **Day of week analysis** - Finding the best/worst days for different directions
|
||||
5. **Custom metrics** - Extending the analysis with domain-specific metrics
|
||||
6. **Visualization tools** - Creating comprehensive performance visualizations
|
||||
7. **Pattern recognition** - Identifying winning/losing streaks and patterns
|
||||
8. **Comprehensive reporting** - Generating detailed performance reports
|
||||
|
||||
The framework is designed to be modular, allowing you to pick and choose the specific analyses most relevant to your trading strategy evaluation needs.
|
||||
Reference in New Issue
Block a user