Files
Encelado/TradingBot/Components/Pages/TradingControl.razor
Alberto Balbo 64f3511695 Nuove: multi-strategy, indicatori avanzati, posizioni
- Sidebar portfolio con metriche dettagliate (Totale, Investito, Disponibile, P&L, ROI) e aggiornamento real-time
- Sistema multi-strategia: 8 strategie assegnabili per asset, voting decisionale, pagina Trading Control
- Nuova pagina Posizioni: gestione, chiusura manuale, P&L non realizzato, notifiche
- Sistema indicatori tecnici: 7+ indicatori configurabili, segnali real-time, raccomandazioni, storico segnali
- Refactoring TradingBotService per capitale, P&L, ROI, eventi
- Nuovi modelli e servizi per strategie/indicatori, persistenza configurazioni
- UI/UX: navigazione aggiornata, widget, modali, responsive
- Aggiornamento README e CHANGELOG con tutte le novità
2026-01-06 17:49:07 +01:00

556 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page "/trading-control"
@using TradingBot.Services
@using TradingBot.Models
@inject TradingStrategiesService StrategiesService
@inject TradingBotService BotService
@implements IDisposable
@rendermode InteractiveServer
<PageTitle>Trading Control - TradingBot</PageTitle>
<div class="trading-control-page">
<div class="page-header">
<div>
<h1>Trading Control</h1>
<p class="subtitle">Gestisci le strategie di trading per ogni asset</p>
</div>
<div class="header-stats">
<div class="stat-item">
<span class="stat-label">Asset Attivi</span>
<span class="stat-value">@activeAssets</span>
</div>
<div class="stat-item">
<span class="stat-label">Strategie in Uso</span>
<span class="stat-value">@totalStrategies</span>
</div>
</div>
</div>
<!-- Assets Grid -->
<div class="assets-control-grid">
@foreach (var asset in BotService.AssetConfigurations.Values.OrderBy(a => a.Symbol))
{
var mapping = StrategiesService.GetAssetMapping(asset.Symbol);
var engineStatus = StrategiesService.GetEngineStatus(asset.Symbol);
var isActive = mapping?.IsActive ?? false;
<div class="asset-control-card @(isActive ? "active" : "")">
<div class="asset-header">
<div class="asset-info">
<h3>@asset.Symbol</h3>
<span class="asset-name">@asset.Name</span>
</div>
<div class="asset-controls">
<label class="toggle-switch">
<input type="checkbox"
checked="@isActive"
@onchange="(e) => ToggleAsset(asset.Symbol, (bool)e.Value!)"
disabled="@((mapping?.StrategyIds.Count ?? 0) == 0)" />
<span class="toggle-slider"></span>
</label>
</div>
</div>
@if (mapping != null && mapping.StrategyIds.Count > 0)
{
<div class="assigned-strategies">
<h4>Strategie Assegnate (@mapping.StrategyIds.Count)</h4>
<div class="strategies-list">
@foreach (var strategyId in mapping.StrategyIds)
{
var strategyInfo = availableStrategies[strategyId];
<div class="strategy-badge">
<span class="strategy-name">@strategyInfo.Name</span>
<span class="strategy-category">@strategyInfo.Category</span>
<button class="btn-remove" @onclick="() => RemoveStrategy(asset.Symbol, strategyId)">
<span class="bi bi-x"></span>
</button>
</div>
}
</div>
</div>
@if (engineStatus?.LastDecision != null && isActive)
{
<div class="last-decision">
<h4>Ultima Decisione</h4>
<div class="decision-info">
<span class="decision-type type-@engineStatus.LastDecision.Decision.ToString().ToLower()">
@engineStatus.LastDecision.Decision
</span>
<span class="decision-confidence">
Confidenza: @engineStatus.LastDecision.Confidence.ToString("F0")%
</span>
</div>
<div class="decision-reason">@engineStatus.LastDecision.Reason</div>
<div class="decision-votes">
<span class="vote buy">Buy: @engineStatus.LastDecision.BuyVotes</span>
<span class="vote sell">Sell: @engineStatus.LastDecision.SellVotes</span>
<span class="vote hold">Hold: @engineStatus.LastDecision.HoldVotes</span>
</div>
</div>
}
}
<div class="asset-actions">
<button class="btn-primary" @onclick="() => OpenStrategySelector(asset.Symbol, asset.Name)">
<span class="bi bi-plus-lg"></span>
@((mapping?.StrategyIds.Count ?? 0) > 0 ? "Gestisci Strategie" : "Assegna Strategie")
</button>
</div>
</div>
}
</div>
<!-- Strategy Selector Modal -->
@if (showStrategySelector)
{
<div class="modal-overlay" @onclick="CloseStrategySelector">
<div class="modal-dialog large" @onclick:stopPropagation="true">
<div class="modal-header">
<h3>Gestisci Strategie - @selectedAssetSymbol</h3>
<button class="btn-close" @onclick="CloseStrategySelector">×</button>
</div>
<div class="modal-body">
<div class="strategies-selector">
@foreach (var category in availableStrategies.Values.Select(s => s.Category).Distinct().OrderBy(c => c))
{
<div class="category-section">
<h4 class="category-title">@category</h4>
<div class="category-strategies">
@foreach (var strategy in availableStrategies.Values.Where(s => s.Category == category))
{
var isSelected = selectedStrategies.Contains(strategy.Id);
<div class="strategy-option @(isSelected ? "selected" : "")"
@onclick="() => ToggleStrategy(strategy.Id)">
<div class="strategy-option-header">
<div class="strategy-checkbox">
<input type="checkbox" checked="@isSelected" />
</div>
<div class="strategy-details">
<h5>@strategy.Name</h5>
<p>@strategy.Description</p>
</div>
</div>
<div class="strategy-meta">
<span class="risk-badge risk-@strategy.RiskLevel.ToString().ToLower()">
@strategy.RiskLevel Risk
</span>
<span class="timeframe-badge">
@strategy.RecommendedTimeFrame
</span>
</div>
</div>
}
</div>
</div>
}
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" @onclick="CloseStrategySelector">Annulla</button>
<button class="btn-primary" @onclick="SaveStrategies">
<span class="bi bi-check-lg"></span>
Salva (@selectedStrategies.Count strategie)
</button>
</div>
</div>
</div>
}
</div>
<style>
.trading-control-page {
display: flex;
flex-direction: column;
gap: 2rem;
}
.header-stats {
display: flex;
gap: 2rem;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
color: #94a3b8;
font-weight: 600;
}
.stat-value {
font-size: 1.5rem;
font-weight: 700;
color: #6366f1;
}
.assets-control-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 1.5rem;
}
.asset-control-card {
background: #1a1f3a;
border-radius: 0.75rem;
border: 1px solid rgba(99, 102, 241, 0.2);
padding: 1.5rem;
transition: all 0.3s ease;
}
.asset-control-card.active {
border-color: rgba(16, 185, 129, 0.5);
background: linear-gradient(135deg, #1a1f3a 0%, rgba(16, 185, 129, 0.05) 100%);
}
.asset-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1.5rem;
}
.asset-info h3 {
font-size: 1.5rem;
font-weight: 700;
color: #e2e8f0;
margin: 0 0 0.25rem 0;
}
.asset-name {
color: #94a3b8;
font-size: 0.875rem;
}
.assigned-strategies {
margin-bottom: 1.5rem;
}
.assigned-strategies h4 {
font-size: 0.875rem;
color: #94a3b8;
margin-bottom: 0.75rem;
text-transform: uppercase;
font-weight: 600;
}
.strategies-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.strategy-badge {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(0, 0, 0, 0.3);
border-radius: 0.5rem;
border: 1px solid rgba(99, 102, 241, 0.2);
}
.strategy-name {
flex: 1;
font-weight: 600;
color: #e2e8f0;
}
.strategy-category {
padding: 0.25rem 0.75rem;
background: rgba(99, 102, 241, 0.2);
border-radius: 0.25rem;
color: #6366f1;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
}
.btn-remove {
background: none;
border: none;
color: #ef4444;
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
transition: background 0.2s;
}
.btn-remove:hover {
background: rgba(239, 68, 68, 0.2);
}
.last-decision {
background: rgba(0, 0, 0, 0.3);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.last-decision h4 {
font-size: 0.875rem;
color: #94a3b8;
margin-bottom: 0.75rem;
text-transform: uppercase;
font-weight: 600;
}
.decision-info {
display: flex;
gap: 1rem;
align-items: center;
margin-bottom: 0.75rem;
}
.decision-type {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-weight: 700;
text-transform: uppercase;
font-size: 0.875rem;
}
.type-buy { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.type-sell { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
.type-hold { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
.decision-confidence {
color: #94a3b8;
font-size: 0.875rem;
}
.decision-reason {
color: #cbd5e1;
font-size: 0.875rem;
margin-bottom: 0.75rem;
}
.decision-votes {
display: flex;
gap: 1rem;
}
.vote {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 700;
}
.vote.buy { background: rgba(16, 185, 129, 0.1); color: #10b981; }
.vote.sell { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
.vote.hold { background: rgba(245, 158, 11, 0.1); color: #f59e0b; }
.asset-actions {
display: flex;
gap: 0.75rem;
}
/* Modal Styles */
.modal-dialog.large {
max-width: 900px;
max-height: 80vh;
}
.modal-body {
max-height: 60vh;
overflow-y: auto;
}
.strategies-selector {
display: flex;
flex-direction: column;
gap: 2rem;
}
.category-section {
border-bottom: 1px solid rgba(99, 102, 241, 0.1);
padding-bottom: 1.5rem;
}
.category-section:last-child {
border-bottom: none;
}
.category-title {
font-size: 1rem;
font-weight: 700;
color: #6366f1;
margin-bottom: 1rem;
text-transform: uppercase;
}
.category-strategies {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.strategy-option {
background: rgba(0, 0, 0, 0.3);
border: 2px solid rgba(99, 102, 241, 0.2);
border-radius: 0.5rem;
padding: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.strategy-option:hover {
border-color: rgba(99, 102, 241, 0.5);
background: rgba(99, 102, 241, 0.05);
}
.strategy-option.selected {
border-color: #6366f1;
background: rgba(99, 102, 241, 0.1);
}
.strategy-option-header {
display: flex;
gap: 1rem;
margin-bottom: 0.75rem;
}
.strategy-checkbox input {
width: 20px;
height: 20px;
cursor: pointer;
}
.strategy-details h5 {
font-size: 1rem;
font-weight: 700;
color: #e2e8f0;
margin: 0 0 0.25rem 0;
}
.strategy-details p {
font-size: 0.875rem;
color: #94a3b8;
margin: 0;
line-height: 1.5;
}
.strategy-meta {
display: flex;
gap: 0.75rem;
}
.risk-badge, .timeframe-badge {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
}
.risk-low { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.risk-medium { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
.risk-high { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
.risk-veryhigh { background: rgba(220, 38, 38, 0.3); color: #dc2626; }
.timeframe-badge {
background: rgba(139, 92, 246, 0.2);
color: #8b5cf6;
}
</style>
@code {
private Dictionary<string, StrategyInfo> availableStrategies = new();
private bool showStrategySelector = false;
private string selectedAssetSymbol = "";
private string selectedAssetName = "";
private List<string> selectedStrategies = new();
private int activeAssets = 0;
private int totalStrategies = 0;
protected override void OnInitialized()
{
LoadData();
StrategiesService.OnMappingsChanged += HandleMappingsChanged;
}
private void LoadData()
{
availableStrategies = StrategiesService.GetAvailableStrategies().ToDictionary(k => k.Key, v => v.Value);
var mappings = StrategiesService.GetAllMappings();
activeAssets = mappings.Values.Count(m => m.IsActive);
totalStrategies = mappings.Values.Sum(m => m.StrategyIds.Count);
}
private void ToggleAsset(string symbol, bool active)
{
if (active)
{
StrategiesService.ActivateAsset(symbol);
}
else
{
StrategiesService.DeactivateAsset(symbol);
}
LoadData();
}
private void OpenStrategySelector(string symbol, string name)
{
selectedAssetSymbol = symbol;
selectedAssetName = name;
var mapping = StrategiesService.GetAssetMapping(symbol);
selectedStrategies = mapping?.StrategyIds.ToList() ?? new List<string>();
showStrategySelector = true;
}
private void CloseStrategySelector()
{
showStrategySelector = false;
selectedStrategies.Clear();
}
private void ToggleStrategy(string strategyId)
{
if (selectedStrategies.Contains(strategyId))
{
selectedStrategies.Remove(strategyId);
}
else
{
selectedStrategies.Add(strategyId);
}
}
private void SaveStrategies()
{
StrategiesService.AssignStrategiesToAsset(selectedAssetSymbol, selectedAssetName, selectedStrategies);
CloseStrategySelector();
LoadData();
}
private void RemoveStrategy(string symbol, string strategyId)
{
StrategiesService.RemoveStrategyFromAsset(symbol, strategyId);
LoadData();
}
private void HandleMappingsChanged()
{
LoadData();
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
StrategiesService.OnMappingsChanged -= HandleMappingsChanged;
}
}