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à
This commit is contained in:
555
TradingBot/Components/Pages/TradingControl.razor
Normal file
555
TradingBot/Components/Pages/TradingControl.razor
Normal file
@@ -0,0 +1,555 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user