Phase 3: Copy-Score + Auto-Pause-Kill-Switch (Trader-Intelligence)
Aus UNSEREN geschlossenen Trades (nicht der externen Data-API) berechnet -> jetzt machbar. - TraderScore (pure, getestet): Compute (CopyPnl/ProfitFactor/AvgPnlPerTrade/Count aus realisierten PnLs) + ShouldAutoPause (enabled & count>=minTrades & pnl<=-threshold). - TrackedTrader: CopyPnl30d/CopyProfitFactor/CopyAvgPnlPerTrade/CopyTradeCount30d (mit Erklärungen) + Migration AddTraderCopyScore (auf MySQL angewendet). - CopyTradingState: globale Auto-Pause-Config (AutoPauseMinTrades 10, AutoPauseDrawdownUsd 10). Per-Master-Schalter TrackedTrader.AutoPauseEnabled. - MasterTraderAnalyticsJob.UpdateCopyScoresAndAutoPauseAsync (entkoppelt von der flakigen Master-History-API): Copy-Score je Master (30T), harte Auto-Pause bei Verlust über Schwelle (IsActive=false, Reasoning+Zeitstempel, Threema; Reaktivierung nur manuell). Injiziert ICopyTradeLogRepository + ThreemaService. - MasterTradersView: 4 Copy-Score-Spalten. 193 Tests gruen. Build/Smoke gruen. OFFEN (3.2, API-abhaengig, Zielland): Sniper-Metriken (MedianHold/SellWithin5Min) aus der Data-API (Portierung analyze_snipers.py). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cda5b055aa
commit
4619edbb99
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using PolyTrader.Modules.CopyTrading.Logic;
|
||||
using PolyTrader.Modules.CopyTrading.Persistence;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -17,16 +18,20 @@ namespace PolyTraderSharp.Services
|
||||
private readonly TerminalLogger _logger;
|
||||
private readonly IMasterTraderHistoryRepository _historyRepo;
|
||||
private readonly ITrackedTraderRepository _traderRepo;
|
||||
private readonly ICopyTradeLogRepository _tradeLog;
|
||||
private readonly ThreemaService _threema;
|
||||
private readonly JobStatusRow _jobStatus;
|
||||
private readonly PolymarketApiService _api;
|
||||
|
||||
public MasterTraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, IMasterTraderHistoryRepository historyRepo, ITrackedTraderRepository traderRepo, JobManager jobManager, PolymarketApiService api)
|
||||
public MasterTraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, IMasterTraderHistoryRepository historyRepo, ITrackedTraderRepository traderRepo, ICopyTradeLogRepository tradeLog, ThreemaService threema, JobManager jobManager, PolymarketApiService api)
|
||||
{
|
||||
_state = state;
|
||||
_copyState = copyState;
|
||||
_logger = logger;
|
||||
_historyRepo = historyRepo;
|
||||
_traderRepo = traderRepo;
|
||||
_tradeLog = tradeLog;
|
||||
_threema = threema;
|
||||
_api = api;
|
||||
|
||||
_jobStatus = new JobStatusRow
|
||||
@@ -187,11 +192,61 @@ namespace PolyTraderSharp.Services
|
||||
}
|
||||
|
||||
_logger.Info("✅ Master-Trader Historien-Analyse abgeschlossen.");
|
||||
|
||||
// Phase 3: Copy-Score + Auto-Pause – entkoppelt von der (flakigen) Master-History-API,
|
||||
// da aus UNSEREN geschlossenen Trades berechnet.
|
||||
await UpdateCopyScoresAndAutoPauseAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"MasterTraderAnalyticsJob Exception: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 3.1/3.3: Berechnet je Master den Copy-Score (letzte 30 Tage aus unseren
|
||||
/// geschlossenen Copy-Trades) und pausiert Master hart, die über genügend Trades ins Minus
|
||||
/// laufen (nur wenn deren AutoPauseEnabled gesetzt ist). Reaktivierung bewusst nur manuell.
|
||||
/// </summary>
|
||||
private async Task UpdateCopyScoresAndAutoPauseAsync()
|
||||
{
|
||||
var since = DateTime.UtcNow.AddDays(-30);
|
||||
|
||||
foreach (var trader in _copyState.Traders.Values.ToList())
|
||||
{
|
||||
try
|
||||
{
|
||||
var pnls = _tradeLog
|
||||
.Find(t => t.SourceTraderId == trader.Id && t.ClosedAt >= since && !t.IsDemo)
|
||||
.Select(t => t.RealizedPnl);
|
||||
var m = TraderScore.Compute(pnls);
|
||||
|
||||
trader.CopyPnl30d = m.CopyPnl;
|
||||
trader.CopyProfitFactor = m.ProfitFactor;
|
||||
trader.CopyAvgPnlPerTrade = m.AvgPnlPerTrade;
|
||||
trader.CopyTradeCount30d = m.TradeCount;
|
||||
|
||||
bool pause = trader.IsActive && TraderScore.ShouldAutoPause(
|
||||
trader.AutoPauseEnabled, m.TradeCount, m.CopyPnl,
|
||||
_copyState.AutoPauseMinTrades, _copyState.AutoPauseDrawdownUsd);
|
||||
|
||||
if (pause)
|
||||
{
|
||||
trader.IsActive = false;
|
||||
trader.Reasoning = $"[Auto-Pause {DateTime.UtcNow:yyyy-MM-dd HH:mm} UTC] Copy-PnL {m.CopyPnl:F2} USDC über {m.TradeCount} Trades (30T) unter Schwelle (-{_copyState.AutoPauseDrawdownUsd:F0}). Reaktivierung manuell.";
|
||||
_logger.Warning($"🛑 [AUTO-PAUSE] Master '{trader.DisplayName}' deaktiviert. Copy-PnL {m.CopyPnl:F2} / {m.TradeCount} Trades. Reaktivierung nur manuell.");
|
||||
try { await _threema.SendMessageAsync($"🛑 Auto-Pause: Master '{trader.DisplayName}' deaktiviert.\nCopy-PnL 30T: {m.CopyPnl:F2} USDC über {m.TradeCount} Trades.\nReaktivierung manuell."); }
|
||||
catch (Exception ex) { _logger.Error($"Threema Auto-Pause-Benachrichtigung fehlgeschlagen: {ex.Message}"); }
|
||||
}
|
||||
|
||||
_copyState.Traders[trader.Id] = trader; // Hot-Path-State synchron halten
|
||||
_traderRepo.Update(trader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Copy-Score/Auto-Pause für '{trader.DisplayName}' fehlgeschlagen: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user