Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2026-08-02 - Динамическое расширение шагов DCA на основе волатильности
**Инсайт:** Во избежание преждевременного срабатывания усредняющих ордеров (DCA) на резких проливах рынка необходимо динамически расширять шаг DCA (делать целевую маржу NextDCAMargin более широкой/отрицательной) на основе рыночной волатильности. В качестве быстрых метрик волатильности отлично подходят текущий спред (CurrentSpread) и историческая волатильность по сигналам (Volatility).
**Действие:** При расчете условий DCA в DcaProcessor масштабировать NextDCAMargin с помощью динамического коэффициента, вычисляемого как отношение текущего спреда/волатильности к базовым значениям (0.2% для спреда и 4.0 для исторической волатильности), с ограничением максимального расширения (до 5.0x).

## 2026-08-01 - Интеграция реального времени и Web Audio API в Web-интерфейсе
**Инсайт:** Автовоспроизведение звуков в современных браузерах блокируется до совершения первого пользовательского действия (клик/интеракция). Для реализации надежного звукового оповещения о сделках необходимо: 1) Динамически генерировать звук через Web Audio API, избавляя проект от внешних аудио-файлов, снижая нагрузку и убирая риск 404 ошибок; 2) Добавлять элементы интеракции (например, кнопку проверки звука "Test Audio") для инициализации и возобновления работы AudioContext со стороны пользователя; 3) Давать возможность отключения звуков в localStorage.
**Действие:** При интеграции аудио-оповещений в Razor-представления использовать чистый ES6 JS с динамическим синтезированием аудио-частот через `OscillatorNode` и `GainNode` на событии клика для обхода блокировок воспроизведения.
Expand Down
111 changes: 111 additions & 0 deletions IntelliTrader.Trading.Tests/TradingTimedTaskTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,116 @@ public void DcaProcessor_ScalesCostBasedOnGlobalRating()
opt.Metadata != null &&
opt.Metadata.BoughtGlobalRating == 0.5)), Times.Once());
}

[Fact]
public void DcaProcessor_WidensSpacingUnderHighSpread()
{
// Arrange
var pair = "BTCUSDT";
var pairConfig = new Mock<IPairConfig>();
pairConfig.Setup(c => c.NextDCAMargin).Returns(-3.0m);
pairConfig.Setup(c => c.BuyEnabled).Returns(true);
pairConfig.Setup(c => c.BuyMultiplier).Returns(1.5m);
pairConfig.Setup(c => c.BuyTrailing).Returns(0m);
pairConfig.Setup(c => c.Rules).Returns(new List<string>());

var safety = new TrailingSafetyOptions
{
MaxTrailingSpread = 0.5m, // baseSpread is MaxTrailingSpread
PauseOnHighSpread = false // We don't pause, we just test widening
};
pairConfig.Setup(c => c.TrailingSafety).Returns(safety);

_tradingService.Setup(s => s.GetPairConfig(pair)).Returns(pairConfig.Object);

var tradingPair = new Mock<ITradingPair>();
tradingPair.Setup(p => p.Pair).Returns(pair);
// CurrentMargin is -4.0%. Base DCA is -3.0%.
// Since CurrentSpread (1.5%) > baseSpread (0.5%), spreadFactor = 1.0 + (1.5 - 0.5) = 2.0.
// effectiveNextDCAMargin = -3.0 * 2.0 = -6.0%.
// Since CurrentMargin (-4.0%) > effective (-6.0%), DCA should NOT trigger yet!
tradingPair.Setup(p => p.CurrentMargin).Returns(-4.0m);
tradingPair.Setup(p => p.CurrentSpread).Returns(1.5m);
tradingPair.Setup(p => p.Cost).Returns(100m);
tradingPair.Setup(p => p.Metadata).Returns(new OrderMetadata());

_account.Setup(a => a.GetTradingPairs(It.IsAny<bool>())).Returns(new List<ITradingPair> { tradingPair.Object });
_tradingService.Setup(s => s.GetPrice(pair, It.IsAny<TradePriceType?>(), It.IsAny<bool>())).Returns(10000m);

_signalsService.Setup(s => s.GetGlobalRating()).Returns((double?)null);
_signalsService.Setup(s => s.GetSignalsByPair(pair)).Returns(new List<ISignal>());

string outMsg = "";
_tradingService.Setup(s => s.CanBuy(It.IsAny<BuyOptions>(), out outMsg)).Returns(true);

var task = new TradingTimedTask(
_loggingService.Object,
_notificationService.Object,
_healthCheckService.Object,
_signalsService.Object,
_orderingService.Object,
_tradingService.Object);

// Act
task.ProcessTradingPairs();

// Assert - Should NOT trigger DCA because spacing is widened to -6.0% and CurrentMargin is -4.0%
_orderingService.Verify(o => o.PlaceBuyOrder(It.IsAny<BuyOptions>()), Times.Never());
}

[Fact]
public void DcaProcessor_WidensSpacingUnderHighSignalVolatility()
{
// Arrange
var pair = "BTCUSDT";
var pairConfig = new Mock<IPairConfig>();
pairConfig.Setup(c => c.NextDCAMargin).Returns(-3.0m);
pairConfig.Setup(c => c.BuyEnabled).Returns(true);
pairConfig.Setup(c => c.BuyMultiplier).Returns(1.5m);
pairConfig.Setup(c => c.BuyTrailing).Returns(0m);
pairConfig.Setup(c => c.Rules).Returns(new List<string>());
pairConfig.Setup(c => c.TrailingSafety).Returns((TrailingSafetyOptions)null!);

_tradingService.Setup(s => s.GetPairConfig(pair)).Returns(pairConfig.Object);

var tradingPair = new Mock<ITradingPair>();
tradingPair.Setup(p => p.Pair).Returns(pair);
// CurrentMargin is -5.0%. Base DCA is -3.0%.
// Signal Volatility is 8.0 (baseline is 4.0).
// signalVolatilityFactor = 1.0 + (8.0 - 4.0) / 4.0 = 2.0.
// effectiveNextDCAMargin = -3.0 * 2.0 = -6.0%.
// Since CurrentMargin (-5.0%) > effective (-6.0%), DCA should NOT trigger yet!
tradingPair.Setup(p => p.CurrentMargin).Returns(-5.0m);
tradingPair.Setup(p => p.CurrentSpread).Returns(0.1m); // low spread, spread factor is 1.0
tradingPair.Setup(p => p.Cost).Returns(100m);
tradingPair.Setup(p => p.Metadata).Returns(new OrderMetadata());

_account.Setup(a => a.GetTradingPairs(It.IsAny<bool>())).Returns(new List<ITradingPair> { tradingPair.Object });
_tradingService.Setup(s => s.GetPrice(pair, It.IsAny<TradePriceType?>(), It.IsAny<bool>())).Returns(10000m);

_signalsService.Setup(s => s.GetGlobalRating()).Returns((double?)null);

var mockSignal = new Mock<ISignal>();
mockSignal.Setup(s => s.Volatility).Returns(8.0);
var signalsList = new List<ISignal> { mockSignal.Object };
_signalsService.Setup(s => s.GetSignalsByPair(pair)).Returns(signalsList);

string outMsg = "";
_tradingService.Setup(s => s.CanBuy(It.IsAny<BuyOptions>(), out outMsg)).Returns(true);

var task = new TradingTimedTask(
_loggingService.Object,
_notificationService.Object,
_healthCheckService.Object,
_signalsService.Object,
_orderingService.Object,
_tradingService.Object);

// Act
task.ProcessTradingPairs();

// Assert - Should NOT trigger DCA because spacing is widened to -6.0% and CurrentMargin is -5.0%
_orderingService.Verify(o => o.PlaceBuyOrder(It.IsAny<BuyOptions>()), Times.Never());
}
}
}
73 changes: 62 additions & 11 deletions IntelliTrader.Trading/Processors/DcaProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,71 @@ public void Process(ITradingPair tradingPair, IPairConfig pairConfig, Concurrent
if (pairConfig.NextDCAMargin != null && pairConfig.BuyEnabled &&
!trailingBuys.ContainsKey(tradingPair.Pair) && !trailingSells.ContainsKey(tradingPair.Pair))
{
if (tradingPair.CurrentMargin <= pairConfig.NextDCAMargin)
// Enforce MaxTrailingSpread safety checks to prevent buying on high-volatility spikes
var safety = pairConfig.TrailingSafety;
if (safety != null && safety.MaxTrailingSpread > 0 && tradingPair.CurrentSpread > safety.MaxTrailingSpread)
{
// Enforce MaxTrailingSpread safety checks to prevent buying on high-volatility spikes
var safety = pairConfig.TrailingSafety;
if (safety != null && safety.MaxTrailingSpread > 0 && tradingPair.CurrentSpread > safety.MaxTrailingSpread)
if (safety.PauseOnHighSpread)
{
if (safety.PauseOnHighSpread)
if (task.LoggingEnabled)
{
if (task.LoggingEnabled)
{
loggingService.Info($"DCA paused for {tradingPair.FormattedName} due to high spread: {tradingPair.CurrentSpread:0.00}%");
}
return;
loggingService.Info($"DCA paused for {tradingPair.FormattedName} due to high spread: {tradingPair.CurrentSpread:0.00}%");
}
return;
}
}

// Dynamic adjustment of DCA price steps based on CurrentSpread or Average True Range (ATR) / Signal Volatility to prevent premature DCA buys in extremely volatile markets
decimal effectiveNextDCAMargin = pairConfig.NextDCAMargin.Value;
decimal spreadFactor = 1.0m;
if (tradingPair.CurrentSpread > 0)
{
decimal baseSpread = 0.2m;
if (pairConfig.TrailingSafety != null && pairConfig.TrailingSafety.MaxTrailingSpread > 0)
{
baseSpread = pairConfig.TrailingSafety.MaxTrailingSpread;
}
if (tradingPair.CurrentSpread > baseSpread)
{
spreadFactor = 1.0m + (tradingPair.CurrentSpread - baseSpread);
}
}

decimal signalVolatilityFactor = 1.0m;
var signals = signalsService.GetSignalsByPair(tradingPair.Pair);
double maxSignalVolatility = 0;
if (signals != null)
{
foreach (var signal in signals)
{
if (signal.Volatility.HasValue && signal.Volatility.Value > maxSignalVolatility)
{
maxSignalVolatility = signal.Volatility.Value;
}
}
}
if (maxSignalVolatility > 0)
{
decimal baseVolatility = 4.0m;
if ((decimal)maxSignalVolatility > baseVolatility)
{
signalVolatilityFactor = 1.0m + (((decimal)maxSignalVolatility - baseVolatility) / baseVolatility);
}
}

decimal volatilityFactor = Math.Max(spreadFactor, signalVolatilityFactor);
if (volatilityFactor > 5.0m)
{
volatilityFactor = 5.0m;
}

if (volatilityFactor > 1.0m)
{
effectiveNextDCAMargin = effectiveNextDCAMargin * volatilityFactor;
}

if (tradingPair.CurrentMargin <= effectiveNextDCAMargin)
{
// Dynamically scale DCA orders based on the global rating
double? globalRating = signalsService.GetGlobalRating();
decimal scalingFactor = 1.0m;
Expand Down Expand Up @@ -68,7 +117,9 @@ public void Process(ITradingPair tradingPair, IPairConfig pairConfig, Concurrent
if (task.LoggingEnabled)
{
loggingService.Info($"DCA triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}, " +
$"Level: {pairConfig.NextDCAMargin:0.00}, Multiplier: {pairConfig.BuyMultiplier}, " +
$"Level (Base): {pairConfig.NextDCAMargin:0.00}, Level (Effective): {effectiveNextDCAMargin:0.00}, " +
$"Volatility Factor: {volatilityFactor:0.00} (Spread: {tradingPair.CurrentSpread:0.00}%, Signal Vol: {maxSignalVolatility:0.00}), " +
$"Multiplier: {pairConfig.BuyMultiplier}, " +
$"Global Rating: {(globalRating.HasValue ? globalRating.Value.ToString("0.00") : "N/A")}, " +
$"Scaling Factor: {scalingFactor:0.00}, Base Cost: {tradingPair.Cost * pairConfig.BuyMultiplier:0.00}, Scaled Cost: {buyOptions.MaxCost:0.00}");
}
Expand Down
2 changes: 1 addition & 1 deletion magda_agent_system/agent_tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@
},
{
"id": "trading-strategy-dca-scaling",
"status": "todo",
"status": "done",
"area": "trading",
"risk": "medium",
"title": "Optimize DCA Step Spacing based on Market Volatility",
Expand Down