Optimize strategies (#23)
* Update Tradingbox + set limit price * Change discord message
This commit is contained in:
@@ -7,23 +7,82 @@ using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Shared.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for strategy combination logic
|
||||
/// </summary>
|
||||
public class StrategyComboConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum percentage of trend strategies that must agree for strong trend (default: 66%)
|
||||
/// </summary>
|
||||
public decimal TrendStrongAgreementThreshold { get; set; } = 0.66m;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum percentage of signal strategies that must agree (default: 50%)
|
||||
/// </summary>
|
||||
public decimal SignalAgreementThreshold { get; set; } = 0.5m;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow signal strategies to override conflicting trends (default: true)
|
||||
/// This is useful for trend reversal signals
|
||||
/// </summary>
|
||||
public bool AllowSignalTrendOverride { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum confidence level to return a signal (default: Low)
|
||||
/// </summary>
|
||||
public Confidence MinimumConfidence { get; set; } = Confidence.Low;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum confidence level required from context strategies (default: Medium)
|
||||
/// Context strategies evaluate market conditions - higher requirements mean more conservative trading
|
||||
/// </summary>
|
||||
public Confidence MinimumContextConfidence { get; set; } = Confidence.Medium;
|
||||
|
||||
/// <summary>
|
||||
/// Default exchange to use when signals don't specify one
|
||||
/// </summary>
|
||||
public TradingExchanges DefaultExchange { get; set; } = TradingExchanges.Binance;
|
||||
}
|
||||
|
||||
public static class TradingBox
|
||||
{
|
||||
private static readonly StrategyComboConfig _defaultConfig = new();
|
||||
|
||||
public static Signal GetSignal(HashSet<Candle> newCandles, HashSet<IStrategy> strategies,
|
||||
HashSet<Signal> previousSignal, int? loopbackPeriod = 1)
|
||||
{
|
||||
return GetSignal(newCandles, strategies, previousSignal, _defaultConfig, loopbackPeriod);
|
||||
}
|
||||
|
||||
public static Signal GetSignal(HashSet<Candle> newCandles, HashSet<IStrategy> strategies,
|
||||
HashSet<Signal> previousSignal, StrategyComboConfig config, int? loopbackPeriod = 1)
|
||||
{
|
||||
var signalOnCandles = new HashSet<Signal>();
|
||||
var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
|
||||
|
||||
foreach (var strategy in strategies)
|
||||
{
|
||||
strategy.UpdateCandles(limitedCandles.ToHashSet());
|
||||
var signals = strategy.Run();
|
||||
|
||||
if (signals == null || signals.Count == 0) continue;
|
||||
if (signals == null || signals.Count == 0)
|
||||
{
|
||||
// For trend and context strategies, lack of signal might be meaningful
|
||||
// Signal strategies are expected to be sparse, so we continue
|
||||
if (strategy.SignalType == SignalType.Signal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// For trend strategies, no signal might mean neutral trend
|
||||
// For context strategies, no signal might mean no restrictions
|
||||
// We'll let the ComputeSignals method handle these cases
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure limitedCandles is ordered chronologically
|
||||
var orderedCandles = limitedCandles.OrderBy(c => c.Date).ToList();
|
||||
|
||||
var loopback = loopbackPeriod.HasValue && loopbackPeriod > 1 ? loopbackPeriod.Value : 1;
|
||||
var candleLoopback = orderedCandles.TakeLast(loopback).ToList();
|
||||
|
||||
@@ -49,63 +108,208 @@ public static class TradingBox
|
||||
}
|
||||
}
|
||||
|
||||
if (signalOnCandles.Count != strategies.Count)
|
||||
return null;
|
||||
// Remove the restrictive requirement that ALL strategies must produce signals
|
||||
// Instead, let ComputeSignals handle the logic based on what we have
|
||||
if (!signalOnCandles.Any())
|
||||
{
|
||||
return null; // No signals from any strategy
|
||||
}
|
||||
|
||||
var data = newCandles.First();
|
||||
return ComputeSignals(strategies, signalOnCandles, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
|
||||
data.Timeframe);
|
||||
data.Timeframe, config);
|
||||
}
|
||||
|
||||
public static Signal ComputeSignals(HashSet<IStrategy> strategies, HashSet<Signal> signalOnCandles, Ticker ticker,
|
||||
Timeframe timeframe)
|
||||
{
|
||||
Signal signal = null;
|
||||
if (strategies.Count > 1)
|
||||
{
|
||||
var trendSignal = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
|
||||
var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
|
||||
var contextStrategiesCount = strategies.Count(s => s.SignalType == SignalType.Context);
|
||||
var validContext = true;
|
||||
return ComputeSignals(strategies, signalOnCandles, ticker, timeframe, _defaultConfig);
|
||||
}
|
||||
|
||||
if (contextStrategiesCount > 0 &&
|
||||
signalOnCandles.Count(s => s.SignalType == SignalType.Context) != contextStrategiesCount)
|
||||
{
|
||||
validContext = false;
|
||||
}
|
||||
|
||||
if (signals.All(s => s.Direction == TradeDirection.Long) &&
|
||||
trendSignal.All(t => t.Direction == TradeDirection.Long) && validContext)
|
||||
{
|
||||
signal = new Signal(
|
||||
ticker,
|
||||
TradeDirection.Long,
|
||||
Confidence.High,
|
||||
signals.Last().Candle,
|
||||
signals.Last().Date,
|
||||
signals.Last().Exchange,
|
||||
StrategyType.Composite, SignalType.Signal);
|
||||
}
|
||||
else if (signals.All(s => s.Direction == TradeDirection.Short) &&
|
||||
trendSignal.All(t => t.Direction == TradeDirection.Short) && validContext)
|
||||
{
|
||||
signal = new Signal(
|
||||
ticker,
|
||||
TradeDirection.Short,
|
||||
Confidence.High,
|
||||
signals.Last().Candle,
|
||||
signals.Last().Date,
|
||||
signals.Last().Exchange,
|
||||
StrategyType.Composite, SignalType.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
public static Signal ComputeSignals(HashSet<IStrategy> strategies, HashSet<Signal> signalOnCandles, Ticker ticker,
|
||||
Timeframe timeframe, StrategyComboConfig config)
|
||||
{
|
||||
if (strategies.Count == 1)
|
||||
{
|
||||
// Only one strategy, we just add the single signal to the bot
|
||||
signal = signalOnCandles.Single();
|
||||
// Only one strategy, return the single signal
|
||||
return signalOnCandles.Single();
|
||||
}
|
||||
|
||||
return signal;
|
||||
// Group signals by type for analysis
|
||||
var signalStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
|
||||
var trendStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
|
||||
var contextStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
|
||||
|
||||
// Context validation - evaluates market conditions based on confidence levels
|
||||
if (!ValidateContextStrategies(strategies, contextStrategies, config))
|
||||
{
|
||||
return null; // Context strategies are blocking the trade
|
||||
}
|
||||
|
||||
// Trend analysis - evaluate overall market direction
|
||||
var trendDirection = EvaluateTrendDirection(trendStrategies, config);
|
||||
|
||||
// Signal analysis - evaluate entry signals
|
||||
var signalDirection = EvaluateSignalDirection(signalStrategies, config);
|
||||
|
||||
// Determine final direction and confidence
|
||||
var (finalDirection, confidence) = DetermineFinalSignal(signalDirection, trendDirection, signalStrategies, trendStrategies, config);
|
||||
|
||||
if (finalDirection == TradeDirection.None || confidence < config.MinimumConfidence)
|
||||
{
|
||||
return null; // No valid signal or below minimum confidence
|
||||
}
|
||||
|
||||
// Create composite signal
|
||||
var lastSignal = signalStrategies.LastOrDefault() ?? trendStrategies.LastOrDefault() ?? contextStrategies.LastOrDefault();
|
||||
|
||||
return new Signal(
|
||||
ticker,
|
||||
finalDirection,
|
||||
confidence,
|
||||
lastSignal?.Candle,
|
||||
lastSignal?.Date ?? DateTime.UtcNow,
|
||||
lastSignal?.Exchange ?? config.DefaultExchange,
|
||||
StrategyType.Composite,
|
||||
SignalType.Signal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates context strategies based on confidence levels indicating market condition quality
|
||||
/// </summary>
|
||||
private static bool ValidateContextStrategies(HashSet<IStrategy> allStrategies, List<Signal> contextSignals, StrategyComboConfig config)
|
||||
{
|
||||
var contextStrategiesCount = allStrategies.Count(s => s.SignalType == SignalType.Context);
|
||||
|
||||
if (contextStrategiesCount == 0)
|
||||
{
|
||||
return true; // No context strategies, no restrictions
|
||||
}
|
||||
|
||||
// Check if we have signals from all context strategies
|
||||
if (contextSignals.Count != contextStrategiesCount)
|
||||
{
|
||||
return false; // Missing context information
|
||||
}
|
||||
|
||||
// All context strategies must meet minimum confidence requirements
|
||||
// This ensures market conditions are suitable for trading
|
||||
return contextSignals.All(s => s.Confidence >= config.MinimumContextConfidence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates trend direction using majority voting with neutral handling
|
||||
/// </summary>
|
||||
private static TradeDirection EvaluateTrendDirection(List<Signal> trendSignals, StrategyComboConfig config)
|
||||
{
|
||||
if (!trendSignals.Any())
|
||||
{
|
||||
return TradeDirection.None; // No trend information available
|
||||
}
|
||||
|
||||
var longCount = trendSignals.Count(s => s.Direction == TradeDirection.Long);
|
||||
var shortCount = trendSignals.Count(s => s.Direction == TradeDirection.Short);
|
||||
var neutralCount = trendSignals.Count(s => s.Direction == TradeDirection.None);
|
||||
|
||||
// Strong trend agreement using configurable threshold
|
||||
var totalTrend = trendSignals.Count;
|
||||
if (longCount > totalTrend * config.TrendStrongAgreementThreshold)
|
||||
return TradeDirection.Long;
|
||||
if (shortCount > totalTrend * config.TrendStrongAgreementThreshold)
|
||||
return TradeDirection.Short;
|
||||
|
||||
// Moderate trend agreement (> 50% but <= strong threshold)
|
||||
if (longCount > shortCount && longCount > neutralCount)
|
||||
return TradeDirection.Long;
|
||||
if (shortCount > longCount && shortCount > neutralCount)
|
||||
return TradeDirection.Short;
|
||||
|
||||
// No clear trend or too many neutrals
|
||||
return TradeDirection.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates signal direction using weighted majority voting
|
||||
/// </summary>
|
||||
private static TradeDirection EvaluateSignalDirection(List<Signal> signalStrategies, StrategyComboConfig config)
|
||||
{
|
||||
if (!signalStrategies.Any())
|
||||
{
|
||||
return TradeDirection.None; // No signal strategies
|
||||
}
|
||||
|
||||
// For signal strategies, we need stronger agreement since they're rare and should be precise
|
||||
var longCount = signalStrategies.Count(s => s.Direction == TradeDirection.Long);
|
||||
var shortCount = signalStrategies.Count(s => s.Direction == TradeDirection.Short);
|
||||
|
||||
// Use configurable agreement threshold for signals
|
||||
var totalSignals = signalStrategies.Count;
|
||||
if (longCount > totalSignals * config.SignalAgreementThreshold)
|
||||
return TradeDirection.Long;
|
||||
if (shortCount > totalSignals * config.SignalAgreementThreshold)
|
||||
return TradeDirection.Short;
|
||||
|
||||
return TradeDirection.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines final signal direction and confidence based on signal and trend analysis
|
||||
/// </summary>
|
||||
private static (TradeDirection Direction, Confidence Confidence) DetermineFinalSignal(
|
||||
TradeDirection signalDirection,
|
||||
TradeDirection trendDirection,
|
||||
List<Signal> signalStrategies,
|
||||
List<Signal> trendStrategies,
|
||||
StrategyComboConfig config)
|
||||
{
|
||||
// Priority 1: If we have signal strategies, they take precedence
|
||||
if (signalDirection != TradeDirection.None)
|
||||
{
|
||||
// Signal strategies have fired - check if trend supports or conflicts
|
||||
if (trendDirection == signalDirection)
|
||||
{
|
||||
// Perfect alignment - signal and trend agree
|
||||
return (signalDirection, Confidence.High);
|
||||
}
|
||||
else if (trendDirection == TradeDirection.None)
|
||||
{
|
||||
// No trend information or neutral trend - medium confidence
|
||||
return (signalDirection, Confidence.Medium);
|
||||
}
|
||||
else if (config.AllowSignalTrendOverride)
|
||||
{
|
||||
// Trend conflicts with signal but we allow override
|
||||
// This could be a trend reversal signal
|
||||
return (signalDirection, Confidence.Low);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trend conflicts and we don't allow override
|
||||
return (TradeDirection.None, Confidence.None);
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Only trend strategies available
|
||||
if (trendDirection != TradeDirection.None)
|
||||
{
|
||||
// Calculate confidence based on trend strength
|
||||
var totalTrend = trendStrategies.Count;
|
||||
var majorityDirection = trendDirection == TradeDirection.Long
|
||||
? trendStrategies.Count(s => s.Direction == TradeDirection.Long)
|
||||
: trendStrategies.Count(s => s.Direction == TradeDirection.Short);
|
||||
|
||||
var agreementPercentage = (decimal)majorityDirection / totalTrend;
|
||||
|
||||
if (agreementPercentage >= 0.8m)
|
||||
return (trendDirection, Confidence.High);
|
||||
else if (agreementPercentage >= config.TrendStrongAgreementThreshold)
|
||||
return (trendDirection, Confidence.Medium);
|
||||
else
|
||||
return (trendDirection, Confidence.Low);
|
||||
}
|
||||
|
||||
// No valid signal found
|
||||
return (TradeDirection.None, Confidence.None);
|
||||
}
|
||||
|
||||
public static MoneyManagement GetBestMoneyManagement(List<Candle> candles, List<Position> positions,
|
||||
|
||||
@@ -33,16 +33,38 @@ public class StDevContext : Strategy
|
||||
return null;
|
||||
|
||||
var lastCandle = stDevCandles.Last();
|
||||
var zScore = lastCandle.ZScore ?? 0;
|
||||
|
||||
if (lastCandle.ZScore is < 1.2 and > (-1.2))
|
||||
// Determine confidence based on Z-score ranges
|
||||
// Lower absolute Z-score = more normal volatility = higher confidence for trading
|
||||
// Higher absolute Z-score = more extreme volatility = lower confidence for trading
|
||||
Confidence confidence;
|
||||
|
||||
if (Math.Abs(zScore) <= 0.5)
|
||||
{
|
||||
AddSignal(lastCandle, TradeDirection.None, Confidence.Medium);
|
||||
// Very low volatility - ideal conditions for trading
|
||||
confidence = Confidence.High;
|
||||
}
|
||||
else if (Math.Abs(zScore) <= 1.0)
|
||||
{
|
||||
// Normal volatility - good conditions for trading
|
||||
confidence = Confidence.Medium;
|
||||
}
|
||||
else if (Math.Abs(zScore) <= 1.5)
|
||||
{
|
||||
// Elevated volatility - caution advised
|
||||
confidence = Confidence.Low;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Bad zscore");
|
||||
// High volatility - trading not recommended
|
||||
confidence = Confidence.None;
|
||||
}
|
||||
|
||||
// Context strategies always return TradeDirection.None
|
||||
// The confidence level indicates the quality of market conditions
|
||||
AddSignal(lastCandle, TradeDirection.None, confidence);
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
|
||||
@@ -6,7 +6,6 @@ using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
@@ -294,7 +293,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Closing Price : {position.Open.Price} \n" +
|
||||
$"Quantity :{position.Open.Quantity} \n" +
|
||||
$"PNL : {position.ProfitAndLoss.Net} $";
|
||||
$"PNL : {position.ProfitAndLoss.Realized} $";
|
||||
}
|
||||
|
||||
private async Task ClosePosition(SocketMessageComponent component, string[] parameters)
|
||||
|
||||
8
src/Managing.Web3Proxy/package-lock.json
generated
8
src/Managing.Web3Proxy/package-lock.json
generated
@@ -4340,7 +4340,7 @@
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.10.0",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
@@ -6598,7 +6598,7 @@
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
@@ -7526,7 +7526,7 @@
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.19.3",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
@@ -7646,7 +7646,7 @@
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
||||
@@ -212,13 +212,14 @@ export const openGmxPositionImpl = async (
|
||||
marketAddress: marketInfo.marketTokenAddress,
|
||||
payTokenAddress: collateralToken.address,
|
||||
collateralTokenAddress: collateralToken.address,
|
||||
allowedSlippageBps: 100, // 0.5% slippage
|
||||
allowedSlippageBps: 50, // 0.5% slippage
|
||||
leverage: leverageBps,
|
||||
skipSimulation: true,
|
||||
referralCodeForTxn: encodeReferralCode("kaigen_ai"),
|
||||
stopLossPrice: stopLossPrice ? numberToBigint(stopLossPrice, 30) : undefined,
|
||||
takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined,
|
||||
acceptablePriceImpactBuffer: 150,
|
||||
limitPrice: limitPrice,
|
||||
};
|
||||
|
||||
if (direction === TradeDirection.Long) {
|
||||
|
||||
@@ -65,7 +65,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
||||
value={takeProfit}
|
||||
onChange={(e: any) => setTakeProfit(e.target.value)}
|
||||
step="0.01"
|
||||
max="20"
|
||||
max="100"
|
||||
type='number'
|
||||
className='input input-bordered'
|
||||
></input>
|
||||
@@ -77,7 +77,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
||||
value={stopLoss}
|
||||
onChange={(e: any) => setStopLoss(e.target.value)}
|
||||
step="0.01"
|
||||
max="20"
|
||||
max="100"
|
||||
type='number'
|
||||
className='input input-bordered'
|
||||
></input>
|
||||
|
||||
@@ -169,6 +169,20 @@ const TradeChart = ({
|
||||
chart.current = createChart(chartRef.current, {
|
||||
crosshair: {
|
||||
mode: CrosshairMode.Normal,
|
||||
vertLine: {
|
||||
color: theme.accent,
|
||||
width: 1,
|
||||
style: LineStyle.Solid,
|
||||
visible: true,
|
||||
labelVisible: true,
|
||||
},
|
||||
horzLine: {
|
||||
color: theme.accent,
|
||||
width: 1,
|
||||
style: LineStyle.Solid,
|
||||
visible: true,
|
||||
labelVisible: true,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
horzLines: {
|
||||
@@ -334,6 +348,7 @@ const TradeChart = ({
|
||||
priceLineColor: theme.info,
|
||||
title: 'SuperTrend',
|
||||
pane: 0,
|
||||
|
||||
})
|
||||
|
||||
const superTrend = strategiesValues.SuperTrend.superTrend?.map((w) => {
|
||||
@@ -409,6 +424,7 @@ const TradeChart = ({
|
||||
precision: 1,
|
||||
type: 'price',
|
||||
},
|
||||
crosshairMarkerVisible: true,
|
||||
})
|
||||
|
||||
paneCount++
|
||||
@@ -453,6 +469,7 @@ const TradeChart = ({
|
||||
precision: 6,
|
||||
type: 'price',
|
||||
},
|
||||
crosshairMarkerVisible: true,
|
||||
})
|
||||
|
||||
const macdData = strategiesValues.MacdCross.macd?.map((w) => {
|
||||
@@ -537,6 +554,7 @@ const TradeChart = ({
|
||||
baseValue: {price: 0, type: 'price'},
|
||||
title: 'ZScore',
|
||||
pane: paneCount,
|
||||
crosshairMarkerVisible: true,
|
||||
})
|
||||
|
||||
const zScore = strategiesValues.StDev.stdDev?.map((w) => {
|
||||
|
||||
Reference in New Issue
Block a user