Aggiunta Bootstrap 5.3.3 (CSS, JS, RTL, mappe) al progetto

Sono stati aggiunti tutti i file principali di Bootstrap 5.3.3, inclusi CSS, JavaScript (bundle, ESM, UMD, minificati), versioni RTL, utility, reboot, griglia e relative mappe delle sorgenti. Questi file abilitano un sistema di design moderno, responsive e accessibile, con supporto per layout LTR e RTL, debugging avanzato tramite source map e tutte le funzionalità di Bootstrap per lo sviluppo dell’interfaccia utente. Nessuna modifica ai file esistenti.
This commit is contained in:
2025-12-12 23:27:28 +01:00
parent d50cb1f7b4
commit d25b4443c0
103 changed files with 69677 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
@using TradingBot.Models
<div class="advanced-chart">
@if (PriceData == null || PriceData.Count < 2)
{
<div class="chart-loading">
<div class="loading-spinner"></div>
<span>In attesa di dati sufficienti...</span>
</div>
}
else
{
<svg viewBox="0 0 @Width @Height" class="chart-svg" preserveAspectRatio="none">
<defs>
<linearGradient id="gradient-@ColorId" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="@Color" stop-opacity="0.4" />
<stop offset="100%" stop-color="@Color" stop-opacity="0.05" />
</linearGradient>
</defs>
<!-- Grid Lines -->
@for (int i = 0; i <= 4; i++)
{
var y = (Height * i / 4.0);
<line x1="0" y1="@y" x2="@Width" y2="@y"
stroke="rgba(51, 65, 85, 0.3)" stroke-width="1" stroke-dasharray="4 4" />
}
<!-- Area Fill -->
@if (!string.IsNullOrEmpty(GetAreaPath()))
{
<path d="@GetAreaPath()" fill="url(#gradient-@ColorId)" />
}
<!-- Line Chart -->
@if (!string.IsNullOrEmpty(GetPolylinePoints()))
{
<polyline fill="none" stroke="@Color" stroke-width="3"
points="@GetPolylinePoints()"
stroke-linecap="round" stroke-linejoin="round" />
}
</svg>
@if (Indicators != null)
{
<div class="indicators-overlay">
<div class="indicator-badge">
<span class="indicator-label">RSI:</span>
<span class="indicator-value @GetRSIClass()">@Indicators.RSI.ToString("F1")</span>
</div>
<div class="indicator-badge">
<span class="indicator-label">MACD:</span>
<span class="indicator-value">@Indicators.MACD.ToString("F2")</span>
</div>
</div>
}
}
</div>
@code {
[Parameter] public List<decimal>? PriceData { get; set; }
[Parameter] public string Color { get; set; } = "#6366f1";
[Parameter] public TechnicalIndicators? Indicators { get; set; }
private int Width = 800;
private int Height = 300;
private string ColorId => Guid.NewGuid().ToString("N").Substring(0, 8);
private string GetPolylinePoints()
{
if (PriceData == null || PriceData.Count < 2) return string.Empty;
try
{
var max = PriceData.Max();
var min = PriceData.Min();
var range = max - min;
if (range == 0) range = max * 0.01m; // 1% range if all values are same
var points = new List<string>();
var padding = Height * 0.1; // 10% padding
var chartHeight = Height - (padding * 2);
for (int i = 0; i < PriceData.Count; i++)
{
var x = (i / (double)(PriceData.Count - 1)) * Width;
var normalizedValue = (double)((PriceData[i] - min) / range);
var y = padding + (chartHeight * (1 - normalizedValue));
points.Add($"{x:F2},{y:F2}");
}
return string.Join(" ", points);
}
catch
{
return string.Empty;
}
}
private string GetAreaPath()
{
var polyline = GetPolylinePoints();
if (string.IsNullOrEmpty(polyline)) return string.Empty;
try
{
var points = polyline.Split(' ');
if (points.Length < 2) return string.Empty;
var firstPoint = points[0].Split(',');
var lastPoint = points[points.Length - 1].Split(',');
return $"M {firstPoint[0]},{Height} L {polyline} L {lastPoint[0]},{Height} Z";
}
catch
{
return string.Empty;
}
}
private string GetRSIClass()
{
if (Indicators == null) return "rsi-neutral";
if (Indicators.RSI > 70) return "rsi-overbought";
if (Indicators.RSI < 30) return "rsi-oversold";
return "rsi-neutral";
}
}

View File

@@ -0,0 +1,82 @@
.advanced-chart {
position: relative;
width: 100%;
height: 100%;
min-height: 300px;
background: #0a0e27;
border-radius: 0.5rem;
overflow: hidden;
}
.chart-svg {
width: 100%;
height: 100%;
display: block;
}
.chart-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
gap: 1rem;
color: #64748b;
}
.loading-spinner {
width: 2rem;
height: 2rem;
border: 3px solid #1e293b;
border-top-color: #6366f1;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.indicators-overlay {
position: absolute;
top: 1rem;
left: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.indicator-badge {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: rgba(15, 23, 42, 0.9);
backdrop-filter: blur(8px);
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 0.375rem;
font-size: 0.75rem;
}
.indicator-label {
color: #94a3b8;
font-weight: 600;
}
.indicator-value {
color: white;
font-weight: 700;
font-family: 'Courier New', monospace;
}
.indicator-value.rsi-overbought {
color: #ef4444;
}
.indicator-value.rsi-oversold {
color: #10b981;
}
.indicator-value.rsi-neutral {
color: #f59e0b;
}

View File

@@ -0,0 +1,344 @@
@using TradingBot.Models
@using TradingBot.Services
@inject TradingBotService BotService
<div class="asset-settings-modal @(IsOpen ? "open" : "")">
<div class="modal-overlay" @onclick="Close"></div>
<div class="modal-content">
<div class="modal-header">
<h3>
<span class="bi bi-gear-fill"></span>
Impostazioni {{Config?.Symbol}}
</h3>
<button class="btn-close" @onclick="Close">
<span class="bi bi-x-lg"></span>
</button>
</div>
@if (Config != null)
{
<div class="modal-body">
<!-- Basic Settings -->
<div class="settings-section">
<h4 class="section-title">Impostazioni Base</h4>
<div class="form-group">
<label>Stato</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox"
@bind="Config.IsEnabled" />
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">
{{Config.IsEnabled ? "Attivo" : "Inattivo"}}
</span>
</div>
</div>
<div class="form-group">
<label>Bilancio Iniziale ($)</label>
<input type="number"
class="form-input"
@bind="Config.InitialBalance"
step="100"
min="0" />
</div>
<div class="form-row">
<div class="form-group">
<label>Bilancio Corrente ($)</label>
<input type="number"
class="form-input"
value="@Config.CurrentBalance"
readonly />
</div>
<div class="form-group">
<label>Holdings</label>
<input type="number"
class="form-input"
value="@Config.CurrentHoldings"
readonly />
</div>
</div>
</div>
<!-- Risk Management -->
<div class="settings-section">
<h4 class="section-title">Gestione del Rischio</h4>
<div class="form-row">
<div class="form-group">
<label>Stop Loss (%)</label>
<input type="number"
class="form-input"
@bind="Config.StopLossPercentage"
step="0.5"
min="0"
max="100" />
</div>
<div class="form-group">
<label>Take Profit (%)</label>
<input type="number"
class="form-input"
@bind="Config.TakeProfitPercentage"
step="0.5"
min="0"
max="100" />
</div>
</div>
<div class="form-group">
<label>Dimensione Massima Posizione ($)</label>
<input type="number"
class="form-input"
@bind="Config.MaxPositionSize"
step="10"
min="0" />
<small class="form-hint">
Massimo valore totale della posizione in dollari
</small>
</div>
</div>
<!-- Trading Constraints -->
<div class="settings-section">
<h4 class="section-title">Limiti di Trading</h4>
<div class="form-row">
<div class="form-group">
<label>Min Trade ($)</label>
<input type="number"
class="form-input"
@bind="Config.MinTradeAmount"
step="1"
min="1" />
</div>
<div class="form-group">
<label>Max Trade ($)</label>
<input type="number"
class="form-input"
@bind="Config.MaxTradeAmount"
step="10"
min="1" />
</div>
</div>
<div class="form-group">
<label>Max Operazioni Giornaliere</label>
<input type="number"
class="form-input"
@bind="Config.MaxDailyTrades"
step="1"
min="1"
max="100" />
<small class="form-hint">
Operazioni oggi: {{Config.DailyTradeCount}} / {{Config.MaxDailyTrades}}
</small>
</div>
</div>
<!-- Strategy Parameters -->
<div class="settings-section">
<h4 class="section-title">Parametri Strategia</h4>
<div class="form-group">
<label>Strategia</label>
<input type="text"
class="form-input"
value="@Config.StrategyName"
readonly />
</div>
@if (Config.StrategyParameters.ContainsKey("ShortPeriod"))
{
<div class="form-row">
<div class="form-group">
<label>Periodo Corto</label>
<input type="number"
class="form-input"
value="@Config.StrategyParameters["ShortPeriod"]"
@onchange="@((e) => UpdateStrategyParameter("ShortPeriod", e.Value))"
step="1"
min="5"
max="50" />
</div>
<div class="form-group">
<label>Periodo Lungo</label>
<input type="number"
class="form-input"
value="@Config.StrategyParameters["LongPeriod"]"
@onchange="@((e) => UpdateStrategyParameter("LongPeriod", e.Value))"
step="1"
min="10"
max="100" />
</div>
</div>
}
@if (Config.StrategyParameters.ContainsKey("SignalThreshold"))
{
<div class="form-group">
<label>Soglia Segnale</label>
<input type="number"
class="form-input"
value="@Config.StrategyParameters["SignalThreshold"]"
@onchange="@((e) => UpdateStrategyParameter("SignalThreshold", e.Value))"
step="0.1"
min="0"
max="5" />
</div>
}
</div>
<!-- Current Stats -->
<div class="settings-section stats-section">
<h4 class="section-title">Statistiche Correnti</h4>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">Profitto Totale</span>
<span class="stat-value @(Config.TotalProfit >= 0 ? "profit" : "loss")">
${{Config.TotalProfit:N2}}
</span>
</div>
<div class="stat-item">
<span class="stat-label">% Profitto</span>
<span class="stat-value @(Config.ProfitPercentage >= 0 ? "profit" : "loss")">
{{Config.ProfitPercentage:F2}}%
</span>
</div>
<div class="stat-item">
<span class="stat-label">Prezzo Medio Entrata</span>
<span class="stat-value">
${{Config.AverageEntryPrice:N2}}
</span>
</div>
<div class="stat-item">
<span class="stat-label">Ultimo Trade</span>
<span class="stat-value">
{{(Config.LastTradeTime?.ToLocalTime().ToString("HH:mm:ss") ?? "Mai")}}
</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" @onclick="ResetToDefaults">
<span class="bi bi-arrow-counterclockwise"></span>
Reset
</button>
<button class="btn-primary" @onclick="SaveSettings">
<span class="bi bi-check-lg"></span>
Salva Modifiche
</button>
</div>
}
</div>
</div>
@code {
[Parameter]
public bool IsOpen { get; set; }
[Parameter]
public string? Symbol { get; set; }
[Parameter]
public EventCallback OnClose { get; set; }
private AssetConfiguration? Config { get; set; }
protected override void OnParametersSet()
{
if (IsOpen && !string.IsNullOrEmpty(Symbol))
{
LoadConfiguration();
}
}
private void LoadConfiguration()
{
if (BotService.AssetConfigurations.TryGetValue(Symbol!, out var config))
{
// Create a copy to avoid modifying the original until saved
Config = new AssetConfiguration
{
Symbol = config.Symbol,
Name = config.Name,
IsEnabled = config.IsEnabled,
InitialBalance = config.InitialBalance,
CurrentBalance = config.CurrentBalance,
CurrentHoldings = config.CurrentHoldings,
AverageEntryPrice = config.AverageEntryPrice,
StrategyName = config.StrategyName,
StrategyParameters = new Dictionary<string, object>(config.StrategyParameters),
MaxPositionSize = config.MaxPositionSize,
StopLossPercentage = config.StopLossPercentage,
TakeProfitPercentage = config.TakeProfitPercentage,
MinTradeAmount = config.MinTradeAmount,
MaxTradeAmount = config.MaxTradeAmount,
MaxDailyTrades = config.MaxDailyTrades,
LastTradeTime = config.LastTradeTime,
DailyTradeCount = config.DailyTradeCount,
DailyTradeCountReset = config.DailyTradeCountReset
};
}
}
private void UpdateStrategyParameter(string key, object? value)
{
if (Config != null && value != null)
{
if (value is string strValue)
{
if (decimal.TryParse(strValue, out var decValue))
{
Config.StrategyParameters[key] = decValue;
}
else if (int.TryParse(strValue, out var intValue))
{
Config.StrategyParameters[key] = intValue;
}
}
}
}
private void ResetToDefaults()
{
if (Config != null)
{
Config.InitialBalance = 1000m;
Config.StopLossPercentage = 5m;
Config.TakeProfitPercentage = 10m;
Config.MaxPositionSize = 100m;
Config.MinTradeAmount = 10m;
Config.MaxTradeAmount = 500m;
Config.MaxDailyTrades = 10;
Config.StrategyParameters = new Dictionary<string, object>
{
{ "ShortPeriod", 10 },
{ "LongPeriod", 30 },
{ "SignalThreshold", 0.5m }
};
}
}
private async Task SaveSettings()
{
if (Config != null && !string.IsNullOrEmpty(Symbol))
{
BotService.UpdateAssetConfiguration(Symbol, Config);
await Close();
}
}
private async Task Close()
{
IsOpen = false;
await OnClose.InvokeAsync();
}
}

View File

@@ -0,0 +1,362 @@
/* Asset Settings Modal */
.asset-settings-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: none;
align-items: center;
justify-content: center;
}
.asset-settings-modal.open {
display: flex;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(4px);
}
.modal-content {
position: relative;
width: 90%;
max-width: 700px;
max-height: 90vh;
background: #0f172a;
border: 1px solid #1e293b;
border-radius: 0.75rem;
display: flex;
flex-direction: column;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
animation: modal-in 0.3s ease;
}
@keyframes modal-in {
from {
opacity: 0;
transform: scale(0.9) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
/* Modal Header */
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
background: #1e293b;
border-bottom: 1px solid #334155;
border-radius: 0.75rem 0.75rem 0 0;
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
color: white;
display: flex;
align-items: center;
gap: 0.75rem;
}
.btn-close {
width: 2rem;
height: 2rem;
border-radius: 0.375rem;
border: none;
background: transparent;
color: #94a3b8;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.btn-close:hover {
background: #334155;
color: white;
}
/* Modal Body */
.modal-body {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
}
/* Settings Section */
.settings-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #1e293b;
}
.settings-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-title {
margin: 0 0 1.5rem 0;
font-size: 1rem;
font-weight: 600;
color: white;
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.875rem;
color: #94a3b8;
}
/* Form Elements */
.form-group {
margin-bottom: 1.5rem;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
color: #cbd5e1;
}
.form-input {
width: 100%;
padding: 0.75rem 1rem;
background: #020617;
border: 1px solid #334155;
border-radius: 0.5rem;
color: white;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.form-input:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.form-input:disabled,
.form-input:read-only {
opacity: 0.6;
cursor: not-allowed;
background: #1e293b;
}
.form-hint {
display: block;
margin-top: 0.375rem;
font-size: 0.75rem;
color: #64748b;
font-style: italic;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
/* Toggle Switch */
.toggle-wrapper {
display: flex;
align-items: center;
gap: 1rem;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 3rem;
height: 1.5rem;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #334155;
transition: 0.3s;
border-radius: 1.5rem;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 1.125rem;
width: 1.125rem;
left: 0.1875rem;
bottom: 0.1875rem;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
.toggle-switch input:checked + .toggle-slider {
background-color: #6366f1;
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(1.5rem);
}
.toggle-label {
font-size: 0.875rem;
font-weight: 600;
color: #cbd5e1;
}
/* Stats Section */
.stats-section {
background: #020617;
border: 1px solid #1e293b;
border-radius: 0.5rem;
padding: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.stat-label {
font-size: 0.75rem;
color: #64748b;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.05em;
}
.stat-value {
font-size: 1.125rem;
font-weight: 700;
color: white;
font-family: monospace;
}
.stat-value.profit {
color: #10b981;
}
.stat-value.loss {
color: #ef4444;
}
/* Modal Footer */
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 1.5rem;
background: #020617;
border-top: 1px solid #1e293b;
border-radius: 0 0 0.75rem 0.75rem;
}
.btn-primary, .btn-secondary {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px -2px rgba(99, 102, 241, 0.3);
}
.btn-secondary {
background: transparent;
color: #94a3b8;
border: 1px solid #334155;
}
.btn-secondary:hover {
background: #1e293b;
border-color: #475569;
color: white;
}
/* Scrollbar Styling */
.modal-body::-webkit-scrollbar {
width: 0.5rem;
}
.modal-body::-webkit-scrollbar-track {
background: #0f172a;
}
.modal-body::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 0.25rem;
}
.modal-body::-webkit-scrollbar-thumb:hover {
background: #475569;
}
/* Responsive */
@media (max-width: 768px) {
.modal-content {
width: 95%;
max-height: 95vh;
}
.form-row {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: 1fr;
}
.modal-header, .modal-body, .modal-footer {
padding: 1rem;
}
}