Files
PolyTraderSharp/services/TraderAnalyticsJob.cs
T
RichardandClaude Opus 4.8 88fc982089 Phase 5.3-ClosedTrade: ClosedTrade -> Modul + ICopyTradeLogRepository
- ClosedTrade (+ ClosedTradeRow) nach src/PolyTrader.Modules.CopyTrading/Models/.
- Neues ICopyTradeLogRepository (+ Mongo-Impl) im Modul: EnsureIndexes, Exists,
  Insert, Find(predicate) auf der closed_trades-Collection.
- closed_trades-Zugriffe der Modul-Services vom App-Shim auf das Repo umgestellt:
  TraderMonitorService, PersistenceService, PolymarketWssClient, TraderAnalyticsJob.
  Shim-/DB-Usings dort entfernt.
- TraderMonitorService nutzt kein _db mehr (auch die uebrigen _db-Guards aus 3d
  entfernt, da Repos immer verfuegbar sind). Verhaltensneutral.
- frm_main (UI) + Program.cs (BsonDocument-Cleanup) bleiben auf _db.
- Build 0 Fehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 22:27:22 +02:00

155 lines
6.6 KiB
C#

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}");
}
});
}
}
}