Phase 5.2: TradingState-Split (Core-State vs. CopyTradingState)
- Core TradingState (in Core): globale Schalter, Accounts, MarketCache, GlobalPnl. - Neuer CopyTradingState (im Modul): Traders, MasterTraderPositions, TraderAnalyticsCache, TotalCopyTrades/GetNextTradeId, PendingOrderTimestamps, SixSharesMinimum. - 10 Konsumenten umgestellt (Program, frm_main, CopyTradingEngine, TraderMonitor, Alchemy, WSS, Snapshot, StartupHydration, beide Analytics-Jobs): Modul-Felder von _state.* auf _copyState.* umgeleitet, CopyTradingState via DI. - Rein mechanische Feld-Umleitung, keine Logikänderung. Build 0 Fehler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
55050a19e5
commit
f8d395b2a3
@@ -16,6 +16,7 @@ namespace PolyTraderSharp.Services
|
||||
public class CopyTradingEngine : BackgroundService
|
||||
{
|
||||
private readonly TradingState _state;
|
||||
private readonly CopyTradingState _copyState;
|
||||
private readonly ChannelReader<CopySignal> _signalReader;
|
||||
private readonly ChannelWriter<ClosedTrade> _closedTradeWriter;
|
||||
private readonly TerminalLogger _logger;
|
||||
@@ -29,6 +30,7 @@ namespace PolyTraderSharp.Services
|
||||
|
||||
public CopyTradingEngine(
|
||||
TradingState state,
|
||||
CopyTradingState copyState,
|
||||
ChannelReader<CopySignal> signalReader,
|
||||
ChannelWriter<ClosedTrade> closedTradeWriter,
|
||||
TerminalLogger logger,
|
||||
@@ -39,6 +41,7 @@ namespace PolyTraderSharp.Services
|
||||
IAccountRepository accountRepo)
|
||||
{
|
||||
_state = state;
|
||||
_copyState = copyState;
|
||||
_signalReader = signalReader;
|
||||
_closedTradeWriter = closedTradeWriter;
|
||||
_logger = logger;
|
||||
@@ -185,7 +188,7 @@ namespace PolyTraderSharp.Services
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_state.Traders.TryGetValue(signal.TraderId, out var trader) || !trader.IsActive)
|
||||
if (!_copyState.Traders.TryGetValue(signal.TraderId, out var trader) || !trader.IsActive)
|
||||
{
|
||||
_logger.TradeReasoning($"\u23f8\ufe0f Trade {signal.Side} [{signal.MarketQuestion}] ignoriert: Trader (ID={signal.TraderId}) nicht gefunden oder inaktiv.");
|
||||
return;
|
||||
@@ -287,7 +290,7 @@ namespace PolyTraderSharp.Services
|
||||
if (account.TotalBalance < 150m) maxAllowed = Math.Min(1.20m, Math.Max(account.AvailableBalance, 0m));
|
||||
else if (account.TotalBalance < 500m) maxAllowed = Math.Min(3.0m, Math.Max(account.AvailableBalance, 0m));
|
||||
|
||||
if (_state.SixSharesMinimum && account.TotalBalance < 500m)
|
||||
if (_copyState.SixSharesMinimum && account.TotalBalance < 500m)
|
||||
{
|
||||
// Adjust maxAllowed to cover at least 6 shares * order limit price.
|
||||
decimal desiredLimitForSix;
|
||||
@@ -459,7 +462,7 @@ namespace PolyTraderSharp.Services
|
||||
// Active day-traders like SwissTony buy 500 shares then sell 2 (0.4%) — we should NOT copy that.
|
||||
// But if they sell 200 of 500 (40%), that's a real exit signal we must copy.
|
||||
string masterPosKey = $"{signal.TraderId}_{inPortfolio.TokenId}";
|
||||
if (_state.MasterTraderPositions.TryGetValue(masterPosKey, out var masterPos))
|
||||
if (_copyState.MasterTraderPositions.TryGetValue(masterPosKey, out var masterPos))
|
||||
{
|
||||
decimal masterShares = masterPos.Shares;
|
||||
if (masterShares > 0 && signal.Size > 0)
|
||||
@@ -524,7 +527,7 @@ namespace PolyTraderSharp.Services
|
||||
ExpiryDate = signal.EndDate ?? DateTime.UtcNow.AddDays(14)
|
||||
};
|
||||
|
||||
_state.GetNextTradeId();
|
||||
_copyState.GetNextTradeId();
|
||||
|
||||
var finalPos = account.OpenPositions.AddOrUpdate(signal.TokenId, pos, (k, old) =>
|
||||
{
|
||||
@@ -569,7 +572,7 @@ namespace PolyTraderSharp.Services
|
||||
ExpiryDate = signal.EndDate ?? DateTime.UtcNow.AddDays(14)
|
||||
};
|
||||
|
||||
_state.GetNextTradeId();
|
||||
_copyState.GetNextTradeId();
|
||||
account.OpenPositions.AddOrUpdate(signal.TokenId, pos, (k, old) =>
|
||||
{
|
||||
old.Size += pos.Size;
|
||||
@@ -588,12 +591,12 @@ namespace PolyTraderSharp.Services
|
||||
|
||||
// Track order placement time for stale order cleanup
|
||||
string orderKey = $"{account.AccountId}_{signal.TokenId}";
|
||||
_state.PendingOrderTimestamps[orderKey] = (DateTime.UtcNow, signal.TraderId);
|
||||
_copyState.PendingOrderTimestamps[orderKey] = (DateTime.UtcNow, signal.TraderId);
|
||||
|
||||
// Initialize master position tracking with signal size if not yet tracked
|
||||
// The background sync will update with the real value within 30 seconds
|
||||
string masterKey = $"{signal.TraderId}_{signal.TokenId}";
|
||||
_state.MasterTraderPositions.TryAdd(masterKey, (signal.Size, DateTime.UtcNow));
|
||||
_copyState.MasterTraderPositions.TryAdd(masterKey, (signal.Size, DateTime.UtcNow));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -601,7 +604,7 @@ namespace PolyTraderSharp.Services
|
||||
else if (signal.Side == "SELL")
|
||||
{
|
||||
string orderKey = $"{account.AccountId}_{signal.TokenId}";
|
||||
if (_state.PendingOrderTimestamps.TryGetValue(orderKey, out var pendingInfo))
|
||||
if (_copyState.PendingOrderTimestamps.TryGetValue(orderKey, out var pendingInfo))
|
||||
{
|
||||
if ((DateTime.UtcNow - pendingInfo.PlacedAt).TotalSeconds < 20)
|
||||
{
|
||||
@@ -654,7 +657,7 @@ namespace PolyTraderSharp.Services
|
||||
|
||||
var ct = new ClosedTrade
|
||||
{
|
||||
TradeId = _state.GetNextTradeId(),
|
||||
TradeId = _copyState.GetNextTradeId(),
|
||||
AccountId = account.AccountId,
|
||||
SourceTraderId = signal.TraderId,
|
||||
IsDemo = account.IsDemo,
|
||||
@@ -703,7 +706,7 @@ namespace PolyTraderSharp.Services
|
||||
if (result == "OK")
|
||||
{
|
||||
// Track order placement time for stale order cleanup / sync routines
|
||||
_state.PendingOrderTimestamps[orderKey] = (DateTime.UtcNow, signal.TraderId);
|
||||
_copyState.PendingOrderTimestamps[orderKey] = (DateTime.UtcNow, signal.TraderId);
|
||||
|
||||
_logger.Trade($"✅ [LIVE MARKET SELL PLATZIERT] - {account.Name} - MARKET Swept. Gewinne/Verluste in Kürze im API Sync sichtbar.");
|
||||
}
|
||||
@@ -716,7 +719,7 @@ namespace PolyTraderSharp.Services
|
||||
$" Aktion: Position bleibt vorerst im Portfolio erhalten.");
|
||||
|
||||
// Temporären Cooldown (5 Sek) setzen, um Log-Spam durch wiederholte API-Fehler zu vermeiden
|
||||
_state.PendingOrderTimestamps[orderKey] = (DateTime.UtcNow.AddSeconds(-15), signal.TraderId);
|
||||
_copyState.PendingOrderTimestamps[orderKey] = (DateTime.UtcNow.AddSeconds(-15), signal.TraderId);
|
||||
|
||||
// Reverse the TryRemove if it failed, so the next poll can try again
|
||||
account.OpenPositions.TryAdd(signal.TokenId, openPos);
|
||||
|
||||
Reference in New Issue
Block a user