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
34 changes: 26 additions & 8 deletions IntelliTrader.Web/Views/Home/Dashboard.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,27 @@
</div>
</div>

<!-- Дополнительные настройки звука -->
<!-- Дополнительные настройки звука и фильтров -->
<div class="row align-items-center mt-2 pt-2" style="border-top: 1px solid var(--card-border); font-size: 13px;">
<div class="col-md-5 d-flex align-items-center mb-2 mb-md-0">
<div class="col-md-4 d-flex align-items-center mb-2 mb-md-0">
<i class="fas fa-sliders-h mr-2" style="color: #7aa2f7;"></i>
<span class="mr-2" style="white-space: nowrap;">Громкость:</span>
<input type="range" id="soundVolumeSlider" min="0" max="1" step="0.05" value="0.5" style="flex-grow: 1; height: 4px; background: var(--bg-color); outline: none; transition: background 450ms ease-in-out; -webkit-appearance: none; cursor: pointer;" />
<span id="soundVolumeValue" class="ml-2" style="font-weight: bold; color: #7aa2f7; min-width: 40px; text-align: right;">50%</span>
</div>
<div class="col-md-5 d-flex align-items-center mb-2 mb-md-0">
<div class="col-md-4 d-flex align-items-center mb-2 mb-md-0">
<i class="fas fa-music mr-2" style="color: #bb9af3;"></i>
<span class="mr-2" style="white-space: nowrap;">Профиль звука:</span>
<span class="mr-2" style="white-space: nowrap;">Профиль:</span>
<select id="soundProfileSelect" class="form-control form-control-sm" style="background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--input-border); border-radius: 4px; height: 28px; padding: 2px 8px; font-size: 12px; width: auto;">
<option value="chime">Приятный звон (Chime)</option>
<option value="beep">Высокий писк (Beep)</option>
<option value="arcade">Ретро аркада (Arcade)</option>
</select>
</div>
<div class="col-md-2 text-md-right text-muted" style="font-size: 11px;">
Настройки звука
<div class="col-md-4 d-flex align-items-center mb-2 mb-md-0">
<i class="fas fa-filter mr-2" style="color: #e0af68;"></i>
<span class="mr-2" style="white-space: nowrap;">Мин. сумма ($):</span>
<input type="number" id="minTradeSizeInput" min="0" step="1" placeholder="0" class="form-control form-control-sm" style="background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--input-border); border-radius: 4px; height: 28px; padding: 2px 8px; font-size: 12px; width: 90px;" />
</div>
</div>
</div>
Expand All @@ -59,6 +61,7 @@
const soundVolumeSlider = document.getElementById("soundVolumeSlider");
const soundVolumeValue = document.getElementById("soundVolumeValue");
const soundProfileSelect = document.getElementById("soundProfileSelect");
const minTradeSizeInput = document.getElementById("minTradeSizeInput");
const notificationContainer = document.getElementById("notificationContainer");

let isSoundEnabled = localStorage.getItem("realtime_sound_enabled") !== "false";
Expand All @@ -74,10 +77,22 @@
// Load sound profile with fallback to 'chime'
let soundProfile = localStorage.getItem("realtime_sound_profile") || "chime";

// Load min trade size threshold with fallback to 0
let realtimeMinTradeSize = parseFloat(localStorage.getItem("realtime_min_trade_size") || "0");
if (isNaN(realtimeMinTradeSize) || realtimeMinTradeSize < 0) realtimeMinTradeSize = 0;

// Sync UI controls with values
soundVolumeSlider.value = soundVolume;
soundVolumeValue.textContent = Math.round(soundVolume * 100) + "%";
soundProfileSelect.value = soundProfile;
if (minTradeSizeInput) {
minTradeSizeInput.value = realtimeMinTradeSize > 0 ? realtimeMinTradeSize : "";
minTradeSizeInput.addEventListener("input", function () {
const val = parseFloat(minTradeSizeInput.value) || 0;
realtimeMinTradeSize = val < 0 ? 0 : val;
localStorage.setItem("realtime_min_trade_size", realtimeMinTradeSize);
});
}

updateSoundButtonUI();

Expand Down Expand Up @@ -332,8 +347,11 @@
knownTrades.add(tradeId);

if (!isFirstLoad) {
hasNewTrade = true;
showToastNotification(trade);
const tradeCost = trade.Cost ? parseFloat(trade.Cost) : (parseFloat(trade.Amount || 0) * parseFloat(trade.SellPrice || trade.AveragePrice || 0));
if (tradeCost >= realtimeMinTradeSize) {
hasNewTrade = true;
showToastNotification(trade);
}
}
}
});
Expand Down
77 changes: 77 additions & 0 deletions IntelliTrader.Web/Views/Home/Settings.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,35 @@
</div>
</div>

<div class="configs mt-4" style="background-color: var(--card-bg) !important; border: 1px solid var(--border-color) !important; color: var(--text-color) !important; padding: 20px; border-radius: 4px; margin-top: 20px;">
<h2>Уведомления и Звук</h2>
<div style="margin-bottom: 15px; display: flex; align-items: center; flex-wrap: wrap;">
<label style="margin-right: 15px; margin-bottom: 0; font-weight: bold;">Звуковые оповещения:</label>
<select id="settingsSoundToggle" class="form-control form-control-sm" style="width: auto; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--input-border); border-radius: 4px; margin-right: 20px;">
<option value="true">Включены</option>
<option value="false">Отключены</option>
</select>
</div>
<div style="margin-bottom: 15px; display: flex; align-items: center; flex-wrap: wrap;">
<label style="margin-right: 15px; margin-bottom: 0; font-weight: bold;">Громкость звука:</label>
<input type="range" id="settingsSoundVolumeSlider" min="0" max="1" step="0.05" value="0.5" style="width: 150px; height: 4px; margin-right: 10px;" />
<span id="settingsSoundVolumeValue" style="font-weight: bold; color: #7aa2f7; min-width: 40px;">50%</span>
</div>
<div style="margin-bottom: 15px; display: flex; align-items: center; flex-wrap: wrap;">
<label style="margin-right: 15px; margin-bottom: 0; font-weight: bold;">Профиль звука:</label>
<select id="settingsSoundProfileSelect" class="form-control form-control-sm" style="width: auto; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--input-border); border-radius: 4px;">
<option value="chime">Приятный звон (Chime)</option>
<option value="beep">Высокий писк (Beep)</option>
<option value="arcade">Ретро аркада (Arcade)</option>
</select>
</div>
<div style="margin-bottom: 10px; display: flex; align-items: center; flex-wrap: wrap;">
<label style="margin-right: 15px; margin-bottom: 0; font-weight: bold;">Мин. сумма сделки (USDT):</label>
<input type="number" id="settingsMinTradeSizeInput" min="0" step="1" placeholder="0 (все сделки)" class="form-control form-control-sm" style="width: 140px; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--input-border); border-radius: 4px;" />
<small class="text-muted ml-2">Уведомления и звук сработают только для сделок, стоимость которых равна или превышает указанный порог.</small>
</div>
</div>

<script>
document.addEventListener("DOMContentLoaded", function() {
const select = document.getElementById("settingsThemeSelect");
Expand Down Expand Up @@ -87,6 +116,54 @@
}
});
}

// Real-time Notification Settings
const soundToggle = document.getElementById("settingsSoundToggle");
const volumeSlider = document.getElementById("settingsSoundVolumeSlider");
const volumeValue = document.getElementById("settingsSoundVolumeValue");
const profileSelect = document.getElementById("settingsSoundProfileSelect");
const minTradeInput = document.getElementById("settingsMinTradeSizeInput");

if (soundToggle) {
const soundEnabled = localStorage.getItem("realtime_sound_enabled") !== "false";
soundToggle.value = soundEnabled ? "true" : "false";
soundToggle.addEventListener("change", function () {
localStorage.setItem("realtime_sound_enabled", soundToggle.value === "true");
});
}

if (volumeSlider && volumeValue) {
let volume = localStorage.getItem("realtime_sound_volume");
volume = volume !== null ? parseFloat(volume) : 0.5;
volumeSlider.value = volume;
volumeValue.textContent = Math.round(volume * 100) + "%";

volumeSlider.addEventListener("input", function () {
const val = parseFloat(volumeSlider.value);
volumeValue.textContent = Math.round(val * 100) + "%";
localStorage.setItem("realtime_sound_volume", val);
});
}

if (profileSelect) {
const profile = localStorage.getItem("realtime_sound_profile") || "chime";
profileSelect.value = profile;
profileSelect.addEventListener("change", function () {
localStorage.setItem("realtime_sound_profile", profileSelect.value);
});
}

if (minTradeInput) {
let minSize = parseFloat(localStorage.getItem("realtime_min_trade_size") || "0");
if (isNaN(minSize) || minSize < 0) minSize = 0;
minTradeInput.value = minSize > 0 ? minSize : "";

minTradeInput.addEventListener("input", function () {
const val = parseFloat(minTradeInput.value) || 0;
const finalVal = val < 0 ? 0 : val;
localStorage.setItem("realtime_min_trade_size", finalVal);
});
}
});
</script>

Expand Down
49 changes: 48 additions & 1 deletion magda_agent_system/agent_tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@
},
{
"id": "web-dashboard-custom-alerts",
"status": "todo",
"status": "done",
"area": "web",
"risk": "low",
"title": "Add custom notification threshold settings on Dashboard",
Expand All @@ -473,6 +473,53 @@
"acceptance": [
"Order caching mechanism is integrated and avoids frequent disk reads for historical trade logs."
]
},
{
"id": "trading-trailing-stop-loss-guard",
"status": "todo",
"area": "trading",
"risk": "medium",
"title": "Implement trailing stop loss boundary protection in SellProcessor",
"description": "Add configurable stop loss limits in SellProcessor to limit losses during sudden flash crashes when trailing sell is active.",
"allowed_paths": [
"IntelliTrader.Trading/Processors/SellProcessor.cs",
"magda_agent_system/agent_tasks.json"
],
"acceptance": [
"SellProcessor enforces stop-loss boundaries during crash scenarios",
"Unit tests verify stop loss execution"
]
},
{
"id": "web-dashboard-csv-export",
"status": "todo",
"area": "web",
"risk": "low",
"title": "Add CSV export button for active trading pairs table on Dashboard",
"description": "Add a button on the Web Dashboard to export current active trading pairs and positions into a CSV file.",
"allowed_paths": [
"IntelliTrader.Web/Views/**",
"IntelliTrader.Web/Static/**",
"magda_agent_system/agent_tasks.json"
],
"acceptance": [
"Users can click a button to download the active positions table as a CSV file"
]
},
{
"id": "exchange-websocket-reconnect-backoff",
"status": "todo",
"area": "exchange",
"risk": "medium",
"title": "Implement exponential backoff reconnect policy in BinanceExchangeService",
"description": "Improve exchange connection resilience by adding exponential backoff and jitter to WebSocket re-connection logic.",
"allowed_paths": [
"IntelliTrader.Exchange.Binance/**",
"magda_agent_system/agent_tasks.json"
],
"acceptance": [
"BinanceExchangeService handles WebSocket disconnects gracefully with exponential backoff"
]
}
]
}