Phase 5: CopyTrading-Modul-Migration (Services + Modul-Contract)
- TraderMonitorService, CopyTradingEngine, TraderAnalyticsJob physisch ins Modul-Projekt (src/PolyTrader.Modules.CopyTrading/Services) verschoben. - Neu: CopyTradingModule : IPolyTraderModule — registriert CopyTradingState, die Signal-/Trade-Channels, ICopyTradeLogRepository und die Modul-Services selbst. - Program.cs: Modul-Discovery (foreach module.RegisterServices / RegisterUi); die entsprechenden Direkt-Registrierungen entfernt. StartupHydration laeuft weiterhin als erster HostedService (State-Hydration vor Trading). - MasterTraderAnalyticsJob bleibt vorerst in der App (nutzt noch den App-Shim fuer mt_history -> braucht erst ein History-Repo). RegisterUi noch leer (Modul-Views folgen; Copytrading-UI weiter ueber Legacy erreichbar). - Build 0 Fehler. Launcher zeigt jetzt "Module: 1". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4f6c7fdb86
commit
ff1db2aea0
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using PolyTrader.Modules.CopyTrading.Persistence;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using PolyTraderSharp.Models;
|
||||
|
||||
namespace PolyTraderSharp.Services
|
||||
{
|
||||
public class TraderAnalyticsJob : BackgroundService
|
||||
{
|
||||
private readonly TradingState _state;
|
||||
private readonly CopyTradingState _copyState;
|
||||
private readonly TerminalLogger _logger;
|
||||
private readonly ICopyTradeLogRepository _tradeLog;
|
||||
private readonly JobStatusRow _jobStatus;
|
||||
|
||||
public TraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, ICopyTradeLogRepository tradeLog, JobManager jobManager)
|
||||
{
|
||||
_state = state;
|
||||
_copyState = copyState;
|
||||
_logger = logger;
|
||||
_tradeLog = tradeLog;
|
||||
|
||||
_jobStatus = new JobStatusRow
|
||||
{
|
||||
JobName = "Trader Analytics",
|
||||
Description = "Analysiert Master-Trader-Performance pro Account (letzte 30 Trades, 7D Volumen).",
|
||||
StatusText = "Pending Initial Delay..."
|
||||
};
|
||||
|
||||
_jobStatus.ManualTriggerAction = async () =>
|
||||
{
|
||||
_jobStatus.StatusText = "Running (Manual)...";
|
||||
await RunAnalyticsAsync();
|
||||
_jobStatus.StatusText = "Idle";
|
||||
_jobStatus.LastRun = DateTime.Now;
|
||||
};
|
||||
|
||||
jobManager.RegisterJob(_jobStatus);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
// Initial wait so the application can start smoothly
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
if (_jobStatus.IsEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jobStatus.StatusText = "Running (Scheduled)...";
|
||||
await RunAnalyticsAsync();
|
||||
_jobStatus.LastRun = DateTime.Now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Error in TraderAnalyticsJob: {ex.Message}");
|
||||
_jobStatus.StatusText = "Error!";
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_jobStatus.StatusText != "Error!") _jobStatus.StatusText = "Idle";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_jobStatus.StatusText = "Paused";
|
||||
}
|
||||
|
||||
_jobStatus.NextRun = DateTime.Now.AddHours(6);
|
||||
await Task.Delay(TimeSpan.FromHours(6), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private Task RunAnalyticsAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("🔄 Starte Trader Analytics (7D / Letzte 30 Trades)...");
|
||||
|
||||
_tradeLog.EnsureIndexes();
|
||||
|
||||
DateTime sevenDaysAgo = DateTime.UtcNow.AddDays(-7);
|
||||
|
||||
foreach (var acc in _state.Accounts.Values)
|
||||
{
|
||||
var results = new List<TraderAnalyticsResult>();
|
||||
|
||||
// Find all master traders that this account has copied successfully in their entire history
|
||||
// Or we just find MTs that were copied in the last 7 days?
|
||||
// The requirement says: "Welche Trades ... in den letzten 7 Tagen kopiert ... und wie hoch war die Winrate der letzten 30 Trades"
|
||||
// Thus we only care about MTs that had at least 1 trade in the last 7 days!
|
||||
int accId = acc.AccountId;
|
||||
var recentMTs = _tradeLog.Find(x => x.AccountId == accId && x.ClosedAt >= sevenDaysAgo)
|
||||
.Select(x => x.SourceTraderId)
|
||||
.Distinct()
|
||||
.Where(id => id != 0) // Ignore orphaned historical trades (API resolved/auto-redeem before ID tracking patch)
|
||||
.ToList();
|
||||
|
||||
foreach (var mtId in recentMTs)
|
||||
{
|
||||
var mtInfo = _copyState.Traders.Values.FirstOrDefault(t => t.Id == mtId);
|
||||
string name = mtInfo?.DisplayName ?? $"MT #{mtId}";
|
||||
string address = mtInfo?.WalletAddress ?? "";
|
||||
|
||||
// 1. Trades im 7D Fenster zählen
|
||||
int trades7D = _tradeLog.Find(x => x.AccountId == accId && x.SourceTraderId == mtId && x.ClosedAt >= sevenDaysAgo).Count();
|
||||
|
||||
// 2. Letzte 30 Trades holen
|
||||
var last30 = _tradeLog.Find(x => x.AccountId == accId && x.SourceTraderId == mtId)
|
||||
.OrderByDescending(x => x.ClosedAt)
|
||||
.Take(30)
|
||||
.ToList();
|
||||
|
||||
if (last30.Count == 0) continue;
|
||||
|
||||
decimal pnl30T = last30.Sum(x => x.RealizedPnl);
|
||||
int wins = last30.Count(x => x.RealizedPnl > 0);
|
||||
// Exakt 0 ist kein Win, nur > 0
|
||||
decimal winrate = ((decimal)wins / last30.Count) * 100m;
|
||||
|
||||
results.Add(new TraderAnalyticsResult
|
||||
{
|
||||
AccountId = acc.AccountId,
|
||||
SourceTraderId = mtId,
|
||||
SourceTraderName = name,
|
||||
SourceTraderAddress = address,
|
||||
Winrate30T = winrate,
|
||||
Pnl30T = pnl30T,
|
||||
Trades7D = trades7D
|
||||
});
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
_copyState.TraderAnalyticsCache[acc.AccountId] = results;
|
||||
}
|
||||
|
||||
_logger.Info("✅ Trader Analytics erfolgreich abgeschlossen und im Cache aktualisiert.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"TraderAnalyticsJob Exception: {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user