Update scoring
This commit is contained in:
78
README.md
78
README.md
@@ -389,47 +389,50 @@ The backtest scoring system evaluates strategy performance using a comprehensive
|
|||||||
|
|
||||||
| Component | Weight | Description |
|
| Component | Weight | Description |
|
||||||
|-----------|--------|-------------|
|
|-----------|--------|-------------|
|
||||||
| **Growth Percentage** | 25% | Primary performance metric based on total return |
|
| **Growth Percentage** | 20% | Primary performance metric based on total return |
|
||||||
| **Sharpe Ratio** | 15% | Risk-adjusted return measure |
|
| **Sharpe Ratio** | 15% | Risk-adjusted return measure (multiplied by 100 for accuracy) |
|
||||||
| **Max Drawdown (USD)** | 12% | Maximum capital loss in absolute terms |
|
| **Hodl Comparison** | 10% | Performance vs buy-and-hold strategy |
|
||||||
| **Win Rate** | 15% | Percentage of profitable trades (weighted by trade count) |
|
| **Win Rate** | 12% | Percentage of profitable trades (weighted by trade count) |
|
||||||
| **Profitability Bonus** | 8% | Additional reward for positive returns |
|
| **Profitability Bonus** | 8% | Additional reward for positive returns |
|
||||||
| **Hodl Comparison** | 5% | Performance vs buy-and-hold strategy |
|
|
||||||
| **Trade Count** | 5% | Sufficient trading activity validation |
|
| **Trade Count** | 5% | Sufficient trading activity validation |
|
||||||
| **Recovery Time** | 2% | Time to recover from maximum drawdown |
|
| **Recovery Time** | 2% | Time to recover from maximum drawdown |
|
||||||
| **Test Duration** | 3% | Adequate testing period validation |
|
| **Test Duration** | 3% | Adequate testing period validation |
|
||||||
| **Fees Impact** | 2% | Trading cost efficiency |
|
| **Fees Impact** | 2% | Trading cost efficiency (based on PnL ratio) |
|
||||||
|
| **Risk Adjusted Return** | 23% | PnL vs MaxDD ratio based on trading balance |
|
||||||
|
|
||||||
#### Component Scoring Details
|
#### Component Scoring Details
|
||||||
|
|
||||||
**Growth Percentage (25%)**
|
**Growth Percentage (20%)**
|
||||||
- **Negative Returns**: Linear penalty (20 + growth% × 1.5)
|
- **Negative Returns**: 0 points (no partial credit for losses)
|
||||||
- **0-5%**: Linear scale (0-40 points)
|
- **0-5%**: Linear scale (0-20 points)
|
||||||
- **5-10%**: Accelerated scale (40-100 points)
|
- **5-10%**: Accelerated scale (20-50 points)
|
||||||
- **10%+**: Full score (100 points)
|
- **10-20%**: Linear scale (50-100 points)
|
||||||
|
- **20%+**: Full score (100 points)
|
||||||
|
|
||||||
**Sharpe Ratio (15%)**
|
**Sharpe Ratio (15%)**
|
||||||
|
- **Input**: Sharpe ratio is multiplied by 100 for more accurate scoring
|
||||||
- **Negative**: 0 points
|
- **Negative**: 0 points
|
||||||
- **0-4**: Linear scale (0-100 points)
|
- **0-4**: Linear scale (0-100 points)
|
||||||
- **4+**: Full score (100 points)
|
- **4+**: Full score (100 points)
|
||||||
|
|
||||||
**Max Drawdown USD (12%)**
|
**Hodl Comparison (10%)**
|
||||||
- **0-30%**: Exponential penalty (100 - (drawdown%/30 × 100)^1.5)
|
- **+5%+ better than HODL**: 100 points
|
||||||
- **30%+**: 0 points
|
- **+2-5% better**: 80-100 points
|
||||||
|
- **+0-2% better**: 40-80 points
|
||||||
|
- **-1-0% worse**: 20-40 points
|
||||||
|
- **-2 to -1% worse**: 0-20 points
|
||||||
|
- **-2%+ worse**: 0 points (handled by early exit)
|
||||||
|
|
||||||
**Win Rate (15%)**
|
**Win Rate (12%)**
|
||||||
- **Base Score**: Win rate percentage
|
- **Input**: Win rate as percentage (e.g., 23.45 for 23.45%)
|
||||||
|
- **Base Score**: Win rate converted to decimal (23.45% → 0.2345)
|
||||||
- **Trade Count Factor**: Full significance at 55+ trades, reduced for fewer trades
|
- **Trade Count Factor**: Full significance at 55+ trades, reduced for fewer trades
|
||||||
- **Minimum Trade Penalty**: 50% penalty for <10 trades
|
- **Minimum Trade Penalty**: 50% penalty for <10 trades
|
||||||
|
|
||||||
**Profitability Bonus (8%)**
|
**Profitability Bonus (8%)**
|
||||||
- **Positive Returns**: Logarithmic bonus (50 × (1 - 1/(1 + growth%/30)))
|
- **Positive Returns**: Logarithmic bonus (max 50 points)
|
||||||
- **Negative Returns**: 0 points
|
- **Negative Returns**: 0 points
|
||||||
|
|
||||||
**Hodl Comparison (5%)**
|
|
||||||
- **Outperforms Hodl**: 0-80 points based on margin
|
|
||||||
- **Underperforms Hodl**: 0-20 points based on underperformance
|
|
||||||
|
|
||||||
**Trade Count (5%)**
|
**Trade Count (5%)**
|
||||||
- **<5 trades**: 0 points
|
- **<5 trades**: 0 points
|
||||||
- **5-10 trades**: Linear scale (0-50 points)
|
- **5-10 trades**: Linear scale (0-50 points)
|
||||||
@@ -456,25 +459,41 @@ The backtest scoring system evaluates strategy performance using a comprehensive
|
|||||||
- **Optimal duration**: 3× minimum duration
|
- **Optimal duration**: 3× minimum duration
|
||||||
|
|
||||||
**Fees Impact (2%)**
|
**Fees Impact (2%)**
|
||||||
- **0-2% fees**: Linear penalty (100-50 points)
|
- **Based on**: Fees as percentage of PnL (not initial balance)
|
||||||
- **2-5% fees**: Linear penalty (50-0 points)
|
- **0-10% fees**: Linear scale (100-50 points)
|
||||||
- **5%+ fees**: 0 points
|
- **10-20% fees**: Linear scale (50-0 points)
|
||||||
|
- **20%+ fees**: 0 points
|
||||||
- **Fees > PnL**: 0 points
|
- **Fees > PnL**: 0 points
|
||||||
|
|
||||||
|
**Risk Adjusted Return (23%)**
|
||||||
|
- **Based on**: Trading balance (not initial balance)
|
||||||
|
- **Calculation**: PnL percentage vs MaxDD percentage ratio
|
||||||
|
- **>3:1 ratio**: 100 points
|
||||||
|
- **2-3:1 ratio**: 80-100 points
|
||||||
|
- **1.5-2:1 ratio**: 60-80 points
|
||||||
|
- **1-1.5:1 ratio**: 40-60 points
|
||||||
|
- **0.5-1:1 ratio**: 20-40 points
|
||||||
|
- **<0.5:1 ratio**: 0-20 points
|
||||||
|
|
||||||
#### Dynamic Penalty System
|
#### Dynamic Penalty System
|
||||||
|
|
||||||
The scoring system applies dynamic penalties based on performance thresholds:
|
The scoring system applies dynamic penalties based on performance thresholds:
|
||||||
|
|
||||||
|
**Early Exit Conditions**
|
||||||
|
- **No Trades**: Automatic 0 score
|
||||||
|
- **Negative PnL**: Automatic 0 score
|
||||||
|
- **HODL Underperformance**: Automatic 0 score if >2% worse than HODL
|
||||||
|
|
||||||
**Profitability Rules**
|
**Profitability Rules**
|
||||||
- **Negative Growth**: 10% penalty per 1% loss
|
- **Negative Growth**: 10% penalty per 1% loss
|
||||||
- **Negative Absolute PnL**: 70% penalty
|
|
||||||
- **Low Win Rate**: 50% penalty per 10% below 30% (for 10+ trades)
|
- **Low Win Rate**: 50% penalty per 10% below 30% (for 10+ trades)
|
||||||
- **Low Profit**: 10% penalty per 1% below 2% (for 5+ trades)
|
- **Low Profit**: 10% penalty per 1% below 2% (for 5+ trades)
|
||||||
- **High Drawdown**: 2% penalty per 1% above 20%
|
- **High Drawdown**: 2% penalty per 1% above 20% of trading balance
|
||||||
|
- **Poor Risk/Reward**: 20% penalty per 0.5 ratio above 1.5:1 (MaxDD/PnL)
|
||||||
- **Short Test Duration**: 2% penalty per day below 30 days
|
- **Short Test Duration**: 2% penalty per day below 30 days
|
||||||
|
- **HODL Underperformance**: 30% penalty per 1% underperformance
|
||||||
|
|
||||||
**Special Rules**
|
**Special Rules**
|
||||||
- **No Positions**: Automatic 0 score
|
|
||||||
- **Score Clamping**: Final score clamped between 0-100
|
- **Score Clamping**: Final score clamped between 0-100
|
||||||
- **Error Handling**: Returns 0 for any calculation errors
|
- **Error Handling**: Returns 0 for any calculation errors
|
||||||
|
|
||||||
@@ -482,11 +501,12 @@ The scoring system applies dynamic penalties based on performance thresholds:
|
|||||||
|
|
||||||
The system prioritizes:
|
The system prioritizes:
|
||||||
1. **Consistent profitability** over high-risk gains
|
1. **Consistent profitability** over high-risk gains
|
||||||
2. **Risk management** through drawdown control
|
2. **Risk management** through drawdown control based on trading balance
|
||||||
3. **Statistical significance** through adequate trade counts
|
3. **Statistical significance** through adequate trade counts
|
||||||
4. **Timeframe-appropriate** expectations for recovery and duration
|
4. **Timeframe-appropriate** expectations for recovery and duration
|
||||||
5. **Cost efficiency** through fee management
|
5. **Cost efficiency** through fee management relative to PnL
|
||||||
6. **Realistic performance** through dynamic penalties
|
6. **Realistic performance** through dynamic penalties
|
||||||
|
7. **HODL outperformance** as a baseline requirement
|
||||||
|
|
||||||
This comprehensive approach ensures that high-scoring strategies demonstrate robust, sustainable performance across multiple dimensions rather than relying on single metrics or short-term luck.
|
This comprehensive approach ensures that high-scoring strategies demonstrate robust, sustainable performance across multiple dimensions rather than relying on single metrics or short-term luck.
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,8 @@ public class MessengerService : IMessengerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness, object? bestChromosome)
|
public async Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness,
|
||||||
|
object? bestChromosome)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -263,7 +264,7 @@ public class MessengerService : IMessengerService
|
|||||||
$"📊 Total Trades: {tradeCount}\n" +
|
$"📊 Total Trades: {tradeCount}\n" +
|
||||||
$"💰 Final PnL: ${finalPnl:F2}\n" +
|
$"💰 Final PnL: ${finalPnl:F2}\n" +
|
||||||
$"📈 Growth: {growthPercentage:F1}%\n" +
|
$"📈 Growth: {growthPercentage:F1}%\n" +
|
||||||
$"📉 Max Drawdown: ${maxDrawdown:C}\n" +
|
$"📉 Max Drawdown: ${maxDrawdown:N}\n" +
|
||||||
$"📊 Sharpe Ratio: {sharpeRatio:F2}\n\n" +
|
$"📊 Sharpe Ratio: {sharpeRatio:F2}\n\n" +
|
||||||
$"🆔 Backtest ID: {backtest.Id}";
|
$"🆔 Backtest ID: {backtest.Id}";
|
||||||
|
|
||||||
|
|||||||
@@ -98,16 +98,16 @@ public class BacktestScorer
|
|||||||
{
|
{
|
||||||
return component switch
|
return component switch
|
||||||
{
|
{
|
||||||
"GrowthPercentage" => $"Growth of {p.GrowthPercentage:F2}% (target: 10% for full score)",
|
"GrowthPercentage" => $"Growth of {p.GrowthPercentage:F2}% (target: 20% for full score, 5% for 20 points, 10% for 50 points)",
|
||||||
"SharpeRatio" => $"Sharpe ratio of {p.SharpeRatio:F2} (target: 4.0 for full score)",
|
"SharpeRatio" => $"Sharpe ratio of {p.SharpeRatio * 100:F2}, target: 4.0 for full score",
|
||||||
"HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%)",
|
"HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%, target: +5% for full score)",
|
||||||
"WinRate" => $"Win rate of {p.WinRate:F2} with {p.TradeCount} trades", // Show as decimal
|
"WinRate" => $"Win rate of {p.WinRate:F2}% with {p.TradeCount} trades (significance factor: {Math.Min(1, (p.TradeCount - 5) / 50.0):F2})",
|
||||||
"ProfitabilityBonus" => $"Bonus for positive growth of {p.GrowthPercentage:F2}%",
|
"ProfitabilityBonus" => $"Bonus for positive growth of {p.GrowthPercentage:F2}% (max 50 points)",
|
||||||
"TradeCount" => $"{p.TradeCount} trades executed (minimum 5, optimal 50+)",
|
"TradeCount" => $"{p.TradeCount} trades executed (5-10: 0-50 points, 10-50: 50-100 points, 50+: 100 points)",
|
||||||
"RecoveryTime" => $"Recovery time: {p.MaxDrawdownRecoveryTime.TotalDays:F1} days",
|
"RecoveryTime" => $"Recovery time: {p.MaxDrawdownRecoveryTime.TotalDays:F1} days (timeframe-adjusted max: {GetMaxRecoveryDays(p.Timeframe):F1} days)",
|
||||||
"TestDuration" => $"Test duration: {(p.EndDate - p.StartDate).TotalDays:F1} days",
|
"TestDuration" => $"Test duration: {(p.EndDate - p.StartDate).TotalDays:F1} days (min: {GetMinTestDays(p.Timeframe):F1}, optimal: {GetMinTestDays(p.Timeframe) * 3:F1} days)",
|
||||||
"FeesImpact" => $"Fees: ${p.Fees:F2} ({(p.TotalPnL > 0 ? p.Fees / p.TotalPnL * 100 : 0):F2}% of PnL)",
|
"FeesImpact" => $"Fees: ${p.Fees:F2} ({(p.TotalPnL > 0 ? p.Fees / p.TotalPnL * 100 : 0):F2}% of PnL, target: below 10% for full score)",
|
||||||
"RiskAdjustedReturn" => $"PnL/TradingBalance Drawdown ratio: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1 (MaxDD: ${p.MaxDrawdown:F2} vs PnL: ${p.TotalPnL:F2})",
|
"RiskAdjustedReturn" => $"Risk/Reward: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1 (PnL: ${p.TotalPnL:F2}, MaxDD: ${p.MaxDrawdown:F2}, target: >3:1 for full score)",
|
||||||
_ => $"Component score: {score:F1}"
|
_ => $"Component score: {score:F1}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -126,12 +126,12 @@ public class BacktestScorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Win Rate Validation (Dynamic)
|
// 3. Win Rate Validation (Dynamic)
|
||||||
if (p.WinRate < 0.3 && p.TradeCount > 10)
|
if (p.WinRate < 30 && p.TradeCount > 10) // winRate is percentage, so 30 = 30%
|
||||||
{
|
{
|
||||||
var winRatePenalty = (0.3 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
|
var winRatePenalty = (30 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
|
||||||
var newMultiplier = Math.Max(0.2, 1 - winRatePenalty);
|
var newMultiplier = Math.Max(0.2, 1 - winRatePenalty);
|
||||||
penaltyMultiplier *= newMultiplier;
|
penaltyMultiplier *= newMultiplier;
|
||||||
result.AddPenaltyCheck("Low Win Rate", newMultiplier, $"Win rate of {p.WinRate * 100:F1}% below 30% threshold applied {winRatePenalty:F1}% penalty");
|
result.AddPenaltyCheck("Low Win Rate", newMultiplier, $"Win rate of {p.WinRate:F2}% below 30% threshold applied {winRatePenalty:F1}% penalty");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Minimum Profit Threshold (Dynamic)
|
// 4. Minimum Profit Threshold (Dynamic)
|
||||||
@@ -246,8 +246,9 @@ public class BacktestScorer
|
|||||||
|
|
||||||
private static double CalculateWinRateScore(double winRate, int tradeCount, BacktestScoringResult result)
|
private static double CalculateWinRateScore(double winRate, int tradeCount, BacktestScoringResult result)
|
||||||
{
|
{
|
||||||
// Use winRate as a decimal (e.g., 0.55 for 55%)
|
// winRate is passed as percentage (e.g., 23.45 for 23.45%), convert to decimal for scoring
|
||||||
var baseScore = winRate;
|
var winRateDecimal = winRate / 100.0;
|
||||||
|
var baseScore = winRateDecimal;
|
||||||
|
|
||||||
// Significance factor - more aggressive
|
// Significance factor - more aggressive
|
||||||
var significanceFactor = Math.Min(1, (tradeCount - 5) / 50.0); // Start at 5 trades, full significance at 55 trades
|
var significanceFactor = Math.Min(1, (tradeCount - 5) / 50.0); // Start at 5 trades, full significance at 55 trades
|
||||||
@@ -379,4 +380,33 @@ public class BacktestScorer
|
|||||||
_ => Math.Max(0, riskRewardRatio * 40) // 0-0.5:1: 0-20 points
|
_ => Math.Max(0, riskRewardRatio * 40) // 0-0.5:1: 0-20 points
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper methods for message generation
|
||||||
|
private static double GetMaxRecoveryDays(Timeframe timeframe)
|
||||||
|
{
|
||||||
|
return timeframe switch
|
||||||
|
{
|
||||||
|
Timeframe.FiveMinutes => 3.0,
|
||||||
|
Timeframe.FifteenMinutes => 5.0,
|
||||||
|
Timeframe.ThirtyMinutes => 10.0,
|
||||||
|
Timeframe.OneHour => 15.0,
|
||||||
|
Timeframe.FourHour => 30.0,
|
||||||
|
Timeframe.OneDay => 90.0,
|
||||||
|
_ => 30.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetMinTestDays(Timeframe timeframe)
|
||||||
|
{
|
||||||
|
return timeframe switch
|
||||||
|
{
|
||||||
|
Timeframe.FiveMinutes => 14.0,
|
||||||
|
Timeframe.FifteenMinutes => 28.0,
|
||||||
|
Timeframe.ThirtyMinutes => 56.0,
|
||||||
|
Timeframe.OneHour => 84.0,
|
||||||
|
Timeframe.FourHour => 120.0,
|
||||||
|
Timeframe.OneDay => 90.0,
|
||||||
|
_ => 21.0
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user