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:
Richard
2026-07-01 20:09:50 +02:00
co-authored by Claude Opus 4.8
parent 55050a19e5
commit f8d395b2a3
13 changed files with 174 additions and 127 deletions
+6 -3
View File
@@ -19,17 +19,20 @@ namespace PolyTraderSharp.Services
private const string TransferBatchTopic = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7ce";
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly ServerSettings _settings;
private readonly TraderMonitorService _traderMonitor;
private readonly TerminalLogger _logger;
public AlchemyWebsocketService(
TradingState state,
CopyTradingState copyState,
ServerSettings settings,
TraderMonitorService traderMonitor,
TerminalLogger logger)
{
_state = state;
_copyState = copyState;
_settings = settings;
_traderMonitor = traderMonitor;
_logger = logger;
@@ -96,7 +99,7 @@ namespace PolyTraderSharp.Services
_state.IsAlchemyHealthy = true;
_logger.Info("✅ Alchemy WSS Connected. Dynamic API-Throttling activated.");
var activeTraders = _state.Traders.Values.Where(t => t.IsActive).ToList();
var activeTraders = _copyState.Traders.Values.Where(t => t.IsActive).ToList();
var activeStateHash = string.Join(",", activeTraders.OrderBy(t => t.Id).Select(t => t.WalletAddress.ToLowerInvariant()));
if (activeTraders.Count > 0)
@@ -165,7 +168,7 @@ namespace PolyTraderSharp.Services
while (!loopCts.IsCancellationRequested)
{
await Task.Delay(5000, loopCts.Token);
var currentTraders = _state.Traders.Values.Where(t => t.IsActive).ToList();
var currentTraders = _copyState.Traders.Values.Where(t => t.IsActive).ToList();
var currentHash = string.Join(",", currentTraders.OrderBy(t => t.Id).Select(t => t.WalletAddress.ToLowerInvariant()));
if (currentHash != activeStateHash)
{
@@ -223,7 +226,7 @@ namespace PolyTraderSharp.Services
if (fromTopic == null || toTopic == null) return;
var activeTraders = _state.Traders.Values.Where(t => t.IsActive).ToList();
var activeTraders = _copyState.Traders.Values.Where(t => t.IsActive).ToList();
string? triggeredAddress = null;
foreach (var trader in activeTraders)
+14 -11
View File
@@ -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);
+4 -2
View File
@@ -14,14 +14,16 @@ namespace PolyTraderSharp.Services
public class MasterTraderAnalyticsJob : BackgroundService
{
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly TerminalLogger _logger;
private readonly IMongoDatabase _db;
private readonly JobStatusRow _jobStatus;
private readonly PolymarketApiService _api;
public MasterTraderAnalyticsJob(TradingState state, TerminalLogger logger, IMongoDatabase db, JobManager jobManager, PolymarketApiService api)
public MasterTraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, IMongoDatabase db, JobManager jobManager, PolymarketApiService api)
{
_state = state;
_copyState = copyState;
_logger = logger;
_db = db;
_api = api;
@@ -90,7 +92,7 @@ namespace PolyTraderSharp.Services
historyColl.EnsureIndex(x => x.ClosedAt);
DateTime cutoff7Days = DateTime.UtcNow.AddDays(-7);
var tradersToAnalyze = _state.Traders.Values.Where(t => t.IsActive && !string.IsNullOrEmpty(t.WalletAddress)).ToList();
var tradersToAnalyze = _copyState.Traders.Values.Where(t => t.IsActive && !string.IsNullOrEmpty(t.WalletAddress)).ToList();
foreach (var trader in tradersToAnalyze)
{
+4 -1
View File
@@ -20,6 +20,7 @@ namespace PolyTraderSharp.Services
private const string MarketWssUrl = "wss://ws-subscriptions-clob.polymarket.com/ws/market";
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly ServerSettings _settings;
private readonly PolymarketClobClient _clob;
private readonly TerminalLogger _logger;
@@ -32,6 +33,7 @@ namespace PolyTraderSharp.Services
public PolymarketWssClient(
TradingState state,
CopyTradingState copyState,
ServerSettings settings,
PolymarketClobClient clob,
TerminalLogger logger,
@@ -40,6 +42,7 @@ namespace PolyTraderSharp.Services
IAccountRepository accountRepo)
{
_state = state;
_copyState = copyState;
_settings = settings;
_clob = clob;
_logger = logger;
@@ -266,7 +269,7 @@ namespace PolyTraderSharp.Services
var ct = new ClosedTrade
{
TradeId = _state.GetNextTradeId(),
TradeId = _copyState.GetNextTradeId(),
AccountId = acc.AccountId,
IsDemo = true,
MarketSlug = pos.MarketSlug,
+5 -3
View File
@@ -8,15 +8,17 @@ namespace PolyTraderSharp.Services
public class SnapshotService : BackgroundService
{
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly ILogger<SnapshotService> _logger;
private readonly string _snapshotPath = "snapshot.json";
private readonly TimeSpan _interval = TimeSpan.FromSeconds(30);
private readonly JobStatusRow _jobStatus;
public SnapshotService(TradingState state, ILogger<SnapshotService> logger, JobManager jobManager)
public SnapshotService(TradingState state, CopyTradingState copyState, ILogger<SnapshotService> logger, JobManager jobManager)
{
_state = state;
_copyState = copyState;
_logger = logger;
_jobStatus = new JobStatusRow
@@ -51,7 +53,7 @@ namespace PolyTraderSharp.Services
{
_state.LiveTradingMode = snapshot.LiveMode;
_state.DemoTradingMode = snapshot.DemoMode;
_state.TotalCopyTrades = snapshot.CopyTrades;
_copyState.TotalCopyTrades = snapshot.CopyTrades;
_state.GlobalPnl = snapshot.GlobalPnl;
int restoredPositions = 0;
@@ -124,7 +126,7 @@ namespace PolyTraderSharp.Services
{
LiveMode = _state.LiveTradingMode,
DemoMode = _state.DemoTradingMode,
CopyTrades = _state.TotalCopyTrades,
CopyTrades = _copyState.TotalCopyTrades,
GlobalPnl = _state.GlobalPnl,
OpenPositions = _state.Accounts.ToDictionary(
a => a.Key,
+5 -2
View File
@@ -18,6 +18,7 @@ namespace PolyTraderSharp.Services
public class StartupHydrationService : IHostedService
{
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly IAccountRepository _accountRepo;
private readonly IPositionRepository _positionRepo;
private readonly IMongoDatabase _db;
@@ -25,12 +26,14 @@ namespace PolyTraderSharp.Services
public StartupHydrationService(
TradingState state,
CopyTradingState copyState,
IAccountRepository accountRepo,
IPositionRepository positionRepo,
IMongoDatabase db,
TerminalLogger logger)
{
_state = state;
_copyState = copyState;
_accountRepo = accountRepo;
_positionRepo = positionRepo;
_db = db;
@@ -58,10 +61,10 @@ namespace PolyTraderSharp.Services
var tradersCol = _db.GetCollection<TrackedTrader>("trackers");
foreach (var trd in tradersCol.Find(_ => true).ToList())
{
_state.Traders[trd.Id] = trd;
_copyState.Traders[trd.Id] = trd;
}
_logger.Info($"Startup-Hydration abgeschlossen: {_state.Accounts.Count} Accounts, {_state.Traders.Count} Trader geladen.");
_logger.Info($"Startup-Hydration abgeschlossen: {_state.Accounts.Count} Accounts, {_copyState.Traders.Count} Trader geladen.");
}
catch (System.Exception ex)
{
+5 -3
View File
@@ -13,13 +13,15 @@ namespace PolyTraderSharp.Services
public class TraderAnalyticsJob : BackgroundService
{
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly TerminalLogger _logger;
private readonly IMongoDatabase _db;
private readonly JobStatusRow _jobStatus;
public TraderAnalyticsJob(TradingState state, TerminalLogger logger, IMongoDatabase db, JobManager jobManager)
public TraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, IMongoDatabase db, JobManager jobManager)
{
_state = state;
_copyState = copyState;
_logger = logger;
_db = db;
@@ -108,7 +110,7 @@ namespace PolyTraderSharp.Services
foreach (var mtId in recentMTs)
{
var mtInfo = _state.Traders.Values.FirstOrDefault(t => t.Id == mtId);
var mtInfo = _copyState.Traders.Values.FirstOrDefault(t => t.Id == mtId);
string name = mtInfo?.DisplayName ?? $"MT #{mtId}";
string address = mtInfo?.WalletAddress ?? "";
@@ -141,7 +143,7 @@ namespace PolyTraderSharp.Services
}
// Save to cache
_state.TraderAnalyticsCache[acc.AccountId] = results;
_copyState.TraderAnalyticsCache[acc.AccountId] = results;
}
_logger.Info("✅ Trader Analytics erfolgreich abgeschlossen und im Cache aktualisiert.");
+24 -21
View File
@@ -16,6 +16,7 @@ namespace PolyTraderSharp.Services
public class TraderMonitorService : BackgroundService
{
private readonly TradingState _state;
private readonly CopyTradingState _copyState;
private readonly PolymarketApiService _api;
private readonly PolymarketClobClient _clob;
private readonly ChannelWriter<CopySignal> _signalWriter;
@@ -39,8 +40,9 @@ namespace PolyTraderSharp.Services
private static readonly ConcurrentDictionary<string, (int Count, DateTime LastAttempt)> _restRedeemAttempts = new();
public TraderMonitorService(
TradingState state,
PolymarketApiService api,
TradingState state,
CopyTradingState copyState,
PolymarketApiService api,
PolymarketClobClient clob,
ChannelWriter<CopySignal> signalWriter,
ChannelWriter<ClosedTrade> closedTradeWriter,
@@ -50,6 +52,7 @@ namespace PolyTraderSharp.Services
IMongoDatabase? db = null)
{
_state = state;
_copyState = copyState;
_api = api;
_clob = clob;
_signalWriter = signalWriter;
@@ -102,7 +105,7 @@ namespace PolyTraderSharp.Services
_lastLivePoll = DateTime.UtcNow;
await SyncMasterTraderPositionsAsync(stoppingToken);
_lastMasterPositionPoll = DateTime.UtcNow;
_logger.Info($"Startup: MasterTraderPositions warmup abgeschlossen ({_state.MasterTraderPositions.Count} Einträge).");
_logger.Info($"Startup: MasterTraderPositions warmup abgeschlossen ({_copyState.MasterTraderPositions.Count} Einträge).");
}
catch (Exception ex)
{
@@ -157,7 +160,7 @@ namespace PolyTraderSharp.Services
return;
}
var activeTraders = _state.Traders.Values.Where(t => t.IsActive).ToList();
var activeTraders = _copyState.Traders.Values.Where(t => t.IsActive).ToList();
if (activeTraders.Count == 0) return;
var now = DateTime.UtcNow;
@@ -240,7 +243,7 @@ namespace PolyTraderSharp.Services
public void TriggerFastBlockchainPoll(string txHash, string rpcUrl, string walletAddress)
{
var trader = _state.Traders.Values.FirstOrDefault(t => t.WalletAddress.Equals(walletAddress, StringComparison.OrdinalIgnoreCase));
var trader = _copyState.Traders.Values.FirstOrDefault(t => t.WalletAddress.Equals(walletAddress, StringComparison.OrdinalIgnoreCase));
if (trader == null || !trader.IsActive) return;
if (_state.EnableBlockchainParser)
@@ -391,7 +394,7 @@ namespace PolyTraderSharp.Services
/// </summary>
public void TriggerManualPoll(string walletAddress)
{
var trader = _state.Traders.Values.FirstOrDefault(t => t.WalletAddress.Equals(walletAddress, StringComparison.OrdinalIgnoreCase));
var trader = _copyState.Traders.Values.FirstOrDefault(t => t.WalletAddress.Equals(walletAddress, StringComparison.OrdinalIgnoreCase));
if (trader != null && trader.IsActive)
{
if (!_activeWssPolls.TryAdd(trader.WalletAddress, true))
@@ -625,12 +628,12 @@ namespace PolyTraderSharp.Services
if (size > 0) exitPrice = (investment + realizedPnl) / size;
string orderKey = $"{acc.AccountId}_{asset}";
bool soldByUs = _state.PendingOrderTimestamps.ContainsKey(orderKey);
bool soldByUs = _copyState.PendingOrderTimestamps.ContainsKey(orderKey);
string exitReason = soldByUs ? "Master Trader Sold" : "Manuell Geschlossen / System";
var ctRecord = new ClosedTrade
{
TradeId = _state.GetNextTradeId(),
TradeId = _copyState.GetNextTradeId(),
AccountId = acc.AccountId,
SourceTraderId = resolvedSourceId,
IsDemo = false,
@@ -744,10 +747,10 @@ namespace PolyTraderSharp.Services
// 1. Prüfe PendingOrderTimestamps (CopyTradingEngine hat diese Order kürzlich platziert)
string orderKey = $"{acc.AccountId}_{asset}";
if (_state.PendingOrderTimestamps.TryGetValue(orderKey, out var pending) && pending.SourceTraderId > 0)
if (_copyState.PendingOrderTimestamps.TryGetValue(orderKey, out var pending) && pending.SourceTraderId > 0)
{
resolvedTraderId = pending.SourceTraderId;
if (_state.Traders.TryGetValue(resolvedTraderId, out var pendingTrader))
if (_copyState.Traders.TryGetValue(resolvedTraderId, out var pendingTrader))
{
resolvedTraderName = pendingTrader.DisplayName;
resolvedTraderAddress = pendingTrader.WalletAddress;
@@ -920,12 +923,12 @@ namespace PolyTraderSharp.Services
_logger.Info($"🏆 Live Market {removedPos.MarketQuestion} geschlossen! PnL: ${(realizedPnl):F2}");
string orderKey = $"{acc.AccountId}_{removedPos.TokenId}";
bool soldByUs = _state.PendingOrderTimestamps.ContainsKey(orderKey);
bool soldByUs = _copyState.PendingOrderTimestamps.ContainsKey(orderKey);
string exitReason = soldByUs ? "Master Trader Sold" : "Market Resolved";
var ctRecord = new ClosedTrade
{
TradeId = _state.GetNextTradeId(),
TradeId = _copyState.GetNextTradeId(),
AccountId = acc.AccountId,
SourceTraderId = removedPos.SourceTraderId,
IsDemo = false,
@@ -943,7 +946,7 @@ namespace PolyTraderSharp.Services
ExitReason = exitReason
};
if (soldByUs) _state.PendingOrderTimestamps.TryRemove(orderKey, out _);
if (soldByUs) _copyState.PendingOrderTimestamps.TryRemove(orderKey, out _);
_processedClosures.TryAdd(duplicateKey, true);
_closedTradeWriter.TryWrite(ctRecord);
@@ -970,12 +973,12 @@ namespace PolyTraderSharp.Services
_logger.Info($"🏆 Live Market {removedPos.MarketQuestion} aufgelöst (Fallback)! Auszahlung: ${(exitPrice * removedPos.Size):F2}");
string orderKey = $"{acc.AccountId}_{removedPos.TokenId}";
bool soldByUs = _state.PendingOrderTimestamps.ContainsKey(orderKey);
bool soldByUs = _copyState.PendingOrderTimestamps.ContainsKey(orderKey);
string exitReason = soldByUs ? "Master Trader Sold" : "Market Resolved";
var ctRecord = new ClosedTrade
{
TradeId = _state.GetNextTradeId(),
TradeId = _copyState.GetNextTradeId(),
AccountId = acc.AccountId,
SourceTraderId = removedPos.SourceTraderId,
IsDemo = false,
@@ -993,7 +996,7 @@ namespace PolyTraderSharp.Services
ExitReason = exitReason
};
if (soldByUs) _state.PendingOrderTimestamps.TryRemove(orderKey, out _);
if (soldByUs) _copyState.PendingOrderTimestamps.TryRemove(orderKey, out _);
_processedClosures.TryAdd(duplicateKey, true);
_closedTradeWriter.TryWrite(ctRecord);
@@ -1079,7 +1082,7 @@ namespace PolyTraderSharp.Services
{
if (ct.IsCancellationRequested) break;
if (!_state.Traders.TryGetValue(traderId, out var trader) || string.IsNullOrEmpty(trader.WalletAddress))
if (!_copyState.Traders.TryGetValue(traderId, out var trader) || string.IsNullOrEmpty(trader.WalletAddress))
continue;
var positionSizes = await _api.GetTraderPositionSizesAsync(trader.WalletAddress, tokenIds);
@@ -1089,7 +1092,7 @@ namespace PolyTraderSharp.Services
{
string key = $"{traderId}_{tokenId}";
decimal shares = positionSizes.ContainsKey(tokenId) ? positionSizes[tokenId] : 0m;
_state.MasterTraderPositions[key] = (shares, DateTime.UtcNow);
_copyState.MasterTraderPositions[key] = (shares, DateTime.UtcNow);
}
await Task.Delay(200, ct); // Brief delay between traders to avoid rate limits
@@ -1104,7 +1107,7 @@ namespace PolyTraderSharp.Services
private async Task CleanupStaleOpenOrdersAsync(CancellationToken ct)
{
var keysToProcess = _state.PendingOrderTimestamps.ToArray();
var keysToProcess = _copyState.PendingOrderTimestamps.ToArray();
if (keysToProcess.Length == 0) return;
foreach (var kvp in keysToProcess)
@@ -1119,7 +1122,7 @@ namespace PolyTraderSharp.Services
// Determine timeout based on trader category
int timeoutMinutes = 30; // Default: 30 min
if (_state.Traders.TryGetValue(kvp.Value.SourceTraderId, out var trader) && trader.Category == "HF")
if (_copyState.Traders.TryGetValue(kvp.Value.SourceTraderId, out var trader) && trader.Category == "HF")
{
timeoutMinutes = 3; // HF Trader: 3 min
}
@@ -1146,7 +1149,7 @@ namespace PolyTraderSharp.Services
}
// Remove from tracking regardless (even if cancel failed, we don't want to spam retries)
_state.PendingOrderTimestamps.TryRemove(kvp.Key, out _);
_copyState.PendingOrderTimestamps.TryRemove(kvp.Key, out _);
}
}