From 095c4b64aabb5cadd1c496903cc274f4036dd4a5 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 3 Jul 2026 10:32:52 +0200 Subject: [PATCH] Phase 5: AccountState-Split (Copytrading-Limits -> Modul) - Copytrading-Detail-Einstellungen (PerMarketLimit, MaxBuyPrice, PerMasterLimit, Zeit-Limits, PreRedeemLimit, ProfitTarget, MaxPriceDifference) aus dem Core- AccountState in das Modul-Modell CopyTradingAccountSettings verschoben. - CopyTradingState.AccountSettings + GetAccountSettings(accountId) (Default-safe). - Consumer umgestellt: CopyTradingEngine, TraderMonitorService, PolymarketWssClient, frm_main lesen die Limits jetzt aus den Account-Settings. - Neues Modul-Repo ICopyTradingAccountSettingsRepository (Collection ct_account_settings), in CopyTradingModule registriert. - StartupHydration: Settings laden + EINMALIGE Migration der Alt-Limits aus dem Roh-accounts-Dokument (keine konfigurierten Limits gehen verloren). - Build 0 Fehler. Co-Authored-By: Claude Opus 4.8 --- frm_main.cs | 16 ++--- services/PolymarketWssClient.cs | 10 +-- services/StartupHydrationService.cs | 68 ++++++++++++++++++- src/PolyTrader.Core/Models/AccountState.cs | 33 +-------- .../CopyTradingModule.cs | 3 + .../CopyTradingState.cs | 12 ++++ .../Models/CopyTradingAccountSettings.cs | 47 +++++++++++++ .../ICopyTradingAccountSettingsRepository.cs | 17 +++++ ...ngoCopyTradingAccountSettingsRepository.cs | 27 ++++++++ .../Services/CopyTradingEngine.cs | 29 ++++---- .../Services/TraderMonitorService.cs | 6 +- 11 files changed, 208 insertions(+), 60 deletions(-) create mode 100644 src/PolyTrader.Modules.CopyTrading/Models/CopyTradingAccountSettings.cs create mode 100644 src/PolyTrader.Modules.CopyTrading/Persistence/ICopyTradingAccountSettingsRepository.cs create mode 100644 src/PolyTrader.Modules.CopyTrading/Persistence/MongoCopyTradingAccountSettingsRepository.cs diff --git a/frm_main.cs b/frm_main.cs index 32b49c4..f2a8145 100644 --- a/frm_main.cs +++ b/frm_main.cs @@ -737,34 +737,34 @@ namespace PolyTraderSharp decimal SafeMaxUsd(decimal pct, decimal tot) => tot * (pct / 100m); decimal SafeUtil(decimal curr, decimal maxUsd) => maxUsd > 0 ? (curr / maxUsd) * 100m : 0m; - decimal max6 = SafeMaxUsd(acc.perMaxTime6h, acc.TotalBalance); + decimal max6 = SafeMaxUsd(_copyState.GetAccountSettings(acc.AccountId).perMaxTime6h, acc.TotalBalance); dgv_dashboard_detaillaufzeit.Rows.Add( "< 6h", - $"{acc.perMaxTime6h:F2}%", + $"{_copyState.GetAccountSettings(acc.AccountId).perMaxTime6h:F2}%", $"${max6:F2}", $"{SafeUtil(under6, max6):F2}%", $"${under6:F2}"); - decimal max24 = SafeMaxUsd(acc.perMaxTime24h, acc.TotalBalance); + decimal max24 = SafeMaxUsd(_copyState.GetAccountSettings(acc.AccountId).perMaxTime24h, acc.TotalBalance); dgv_dashboard_detaillaufzeit.Rows.Add( "< 24h", - $"{acc.perMaxTime24h:F2}%", + $"{_copyState.GetAccountSettings(acc.AccountId).perMaxTime24h:F2}%", $"${max24:F2}", $"{SafeUtil(under24, max24):F2}%", $"${under24:F2}"); - decimal max72 = SafeMaxUsd(acc.perMaxTime72h, acc.TotalBalance); + decimal max72 = SafeMaxUsd(_copyState.GetAccountSettings(acc.AccountId).perMaxTime72h, acc.TotalBalance); dgv_dashboard_detaillaufzeit.Rows.Add( "< 72h", - $"{acc.perMaxTime72h:F2}%", + $"{_copyState.GetAccountSettings(acc.AccountId).perMaxTime72h:F2}%", $"${max72:F2}", $"{SafeUtil(under72, max72):F2}%", $"${under72:F2}"); - decimal maxOver72 = SafeMaxUsd(acc.perMaxTimeNone, acc.TotalBalance); + decimal maxOver72 = SafeMaxUsd(_copyState.GetAccountSettings(acc.AccountId).perMaxTimeNone, acc.TotalBalance); dgv_dashboard_detaillaufzeit.Rows.Add( "> 72h", - $"{acc.perMaxTimeNone:F2}%", + $"{_copyState.GetAccountSettings(acc.AccountId).perMaxTimeNone:F2}%", $"${maxOver72:F2}", $"{SafeUtil(over72, maxOver72):F2}%", $"${over72:F2}"); diff --git a/services/PolymarketWssClient.cs b/services/PolymarketWssClient.cs index fd8b26b..eac74eb 100644 --- a/services/PolymarketWssClient.cs +++ b/services/PolymarketWssClient.cs @@ -183,7 +183,7 @@ namespace PolyTraderSharp.Services pos.CurrentValueUsd = pos.Size * price; // Execute Auto-Redeem if config conditions are met - if (acc.PreRedeemLimit > 0 && price >= acc.PreRedeemLimit && acc.IsActive) + if (_copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit > 0 && price >= _copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit && acc.IsActive) { string redeemKey = $"{acc.AccountId}_{assetId}"; // Spam protection: max 2 attempts per position, 5 minutes apart @@ -195,13 +195,13 @@ namespace PolyTraderSharp.Services if (!acc.IsDemo && _state.LiveTradingMode == TradingMode.Active) { - _logger.Trade($"🚨 [AUTO REDEEM] {acc.Name} | {pos.MarketQuestion} | Preis >= {acc.PreRedeemLimit}"); + _logger.Trade($"🚨 [AUTO REDEEM] {acc.Name} | {pos.MarketQuestion} | Preis >= {_copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit}"); // Best effort non-blocking _ = Task.Run(async () => await ExecuteAutoRedeemLive(acc, pos, price)); } else if (acc.IsDemo && _state.DemoTradingMode == TradingMode.Active) { - _logger.Trade($"🚨 [AUTO REDEEM DEMO] {acc.Name} | {pos.MarketQuestion} | Preis >= {acc.PreRedeemLimit}"); + _logger.Trade($"🚨 [AUTO REDEEM DEMO] {acc.Name} | {pos.MarketQuestion} | Preis >= {_copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit}"); _ = Task.Run(() => ExecuteAutoRedeemDemo(acc, pos, price)); } } @@ -227,7 +227,7 @@ namespace PolyTraderSharp.Services try { // The user explicitly requested an exact GTC order using the configured PreRedeemLimit, without slippage - decimal expectedFillPrice = acc.PreRedeemLimit; + decimal expectedFillPrice = _copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit; decimal amountUsdc = Math.Max(pos.Size * expectedFillPrice, 0.01m); // Fire and forget SELL via ClobClient @@ -259,7 +259,7 @@ namespace PolyTraderSharp.Services { _positionRepo.DeleteDemo(acc.AccountId, pos.TokenId); - decimal exactLimitPrice = acc.PreRedeemLimit; + decimal exactLimitPrice = _copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit; decimal exitUsd = pos.Size * exactLimitPrice; decimal realizedPnl = exitUsd - pos.AmountUsd; diff --git a/services/StartupHydrationService.cs b/services/StartupHydrationService.cs index b5e7512..f5b2106 100644 --- a/services/StartupHydrationService.cs +++ b/services/StartupHydrationService.cs @@ -1,9 +1,12 @@ +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; +using MongoDB.Bson; using MongoDB.Driver; using PolyTrader.Core.Persistence; +using PolyTrader.Modules.CopyTrading.Persistence; using PolyTraderSharp.Models; namespace PolyTraderSharp.Services @@ -21,6 +24,7 @@ namespace PolyTraderSharp.Services private readonly CopyTradingState _copyState; private readonly IAccountRepository _accountRepo; private readonly IPositionRepository _positionRepo; + private readonly ICopyTradingAccountSettingsRepository _accountSettingsRepo; private readonly IMongoDatabase _db; private readonly TerminalLogger _logger; @@ -29,6 +33,7 @@ namespace PolyTraderSharp.Services CopyTradingState copyState, IAccountRepository accountRepo, IPositionRepository positionRepo, + ICopyTradingAccountSettingsRepository accountSettingsRepo, IMongoDatabase db, TerminalLogger logger) { @@ -36,6 +41,7 @@ namespace PolyTraderSharp.Services _copyState = copyState; _accountRepo = accountRepo; _positionRepo = positionRepo; + _accountSettingsRepo = accountSettingsRepo; _db = db; _logger = logger; } @@ -44,7 +50,8 @@ namespace PolyTraderSharp.Services { try { - foreach (var acc in _accountRepo.GetAll()) + var accounts = _accountRepo.GetAll(); + foreach (var acc in accounts) { if (acc.IsDemo) { @@ -56,6 +63,8 @@ namespace PolyTraderSharp.Services _state.Accounts[acc.AccountId] = acc; } + HydrateAccountSettings(accounts); + // Trader (TrackedTrader) sind aktuell noch modulnah; bis zum Trader-Repo in // Phase 5 direkt aus der "trackers"-Collection geladen. var tradersCol = _db.GetCollection("trackers"); @@ -74,6 +83,63 @@ namespace PolyTraderSharp.Services return Task.CompletedTask; } + /// + /// Lädt die Copytrading-Account-Settings in den State. Existiert für einen Account noch + /// kein Eintrag, werden die Werte einmalig aus den alten AccountState-Feldern des + /// Roh-Dokuments migriert (damit konfigurierte Limits nach dem Modell-Split erhalten bleiben). + /// + private void HydrateAccountSettings(List accounts) + { + var existing = _accountSettingsRepo.GetAll().ToDictionary(s => s.AccountId); + var rawAccounts = _db.GetCollection("accounts"); + int migrated = 0; + + foreach (var acc in accounts) + { + if (existing.TryGetValue(acc.AccountId, out var s)) + { + _copyState.AccountSettings[acc.AccountId] = s; + continue; + } + + var settings = new CopyTradingAccountSettings { AccountId = acc.AccountId }; + try + { + var raw = rawAccounts.Find(Builders.Filter.Eq("_id", acc.AccountId)).FirstOrDefault(); + if (raw != null) + { + settings.PerMarketLimit = Dec(raw, "PerMarketLimit", settings.PerMarketLimit); + settings.MaxPriceDifference = Dec(raw, "MaxPriceDifference", settings.MaxPriceDifference); + settings.MaxBuyPrice = Dec(raw, "MaxBuyPrice", settings.MaxBuyPrice); + settings.ProfitTarget = Dec(raw, "ProfitTarget", settings.ProfitTarget); + settings.PreRedeemLimit = Dec(raw, "PreRedeemLimit", settings.PreRedeemLimit); + settings.PerMasterLimit = Dec(raw, "PerMasterLimit", settings.PerMasterLimit); + settings.perMaxTime6h = Dec(raw, "perMaxTime6h", settings.perMaxTime6h); + settings.perMaxTime24h = Dec(raw, "perMaxTime24h", settings.perMaxTime24h); + settings.perMaxTime72h = Dec(raw, "perMaxTime72h", settings.perMaxTime72h); + settings.perMaxTimeNone = Dec(raw, "perMaxTimeNone", settings.perMaxTimeNone); + migrated++; + } + } + catch { } + + _accountSettingsRepo.Upsert(settings); + _copyState.AccountSettings[acc.AccountId] = settings; + } + + if (migrated > 0) + _logger.Info($"Copytrading-Account-Settings: {migrated} Account(s) aus Alt-Feldern migriert."); + } + + private static decimal Dec(BsonDocument doc, string field, decimal fallback) + { + if (doc.TryGetValue(field, out var v)) + { + try { return v.ToDecimal(); } catch { } + } + return fallback; + } + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } } diff --git a/src/PolyTrader.Core/Models/AccountState.cs b/src/PolyTrader.Core/Models/AccountState.cs index 1a25364..c1dd19f 100644 --- a/src/PolyTrader.Core/Models/AccountState.cs +++ b/src/PolyTrader.Core/Models/AccountState.cs @@ -51,36 +51,9 @@ namespace PolyTraderSharp.Models public decimal AvailableBalance { get; set; } // Risk Settings - [Category("04. Risk Management")] - public decimal PerMarketLimit { get; set; } = 5.0m; - - [Category("04. Risk Management")] - public decimal MaxPriceDifference { get; set; } = 2.0m; - - [Category("04. Risk Management")] - public decimal MaxBuyPrice { get; set; } = 0.98m; - - [Category("04. Risk Management")] - public decimal ProfitTarget { get; set; } = 50.0m; - - [Category("04. Risk Management")] - public decimal PreRedeemLimit { get; set; } = 0.0m; - - [Category("04. Risk Management")] - public decimal PerMasterLimit { get; set; } = 10.0m; - - // Time limits - [Category("05. Time Limits")] - public decimal perMaxTime6h { get; set; } = 20.0m; - - [Category("05. Time Limits")] - public decimal perMaxTime24h { get; set; } = 20.0m; - - [Category("05. Time Limits")] - public decimal perMaxTime72h { get; set; } = 20.0m; - - [Category("05. Time Limits")] - public decimal perMaxTimeNone { get; set; } = 40.0m; + // Copytrading-Detail-Einstellungen (PerMarketLimit, MaxBuyPrice, Zeit-Limits …) + // liegen bewusst NICHT mehr hier, sondern im Copytrading-Modul + // (CopyTradingAccountSettings), da sie modulspezifisch sind. [Browsable(false)] public ConcurrentDictionary OpenPositions { get; } = new(StringComparer.OrdinalIgnoreCase); diff --git a/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs b/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs index 18da1b2..359dac8 100644 --- a/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs +++ b/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs @@ -36,6 +36,9 @@ namespace PolyTrader.Modules.CopyTrading // Modul-eigener Trade-Log services.AddSingleton(); + // Copytrading-Account-Detail-Einstellungen + services.AddSingleton(); + // Modul-Services (Signalquelle, Ausführung, Analytics) services.AddSingleton(); services.AddHostedService(sp => sp.GetRequiredService()); diff --git a/src/PolyTrader.Modules.CopyTrading/CopyTradingState.cs b/src/PolyTrader.Modules.CopyTrading/CopyTradingState.cs index 9d99769..5d58467 100644 --- a/src/PolyTrader.Modules.CopyTrading/CopyTradingState.cs +++ b/src/PolyTrader.Modules.CopyTrading/CopyTradingState.cs @@ -21,6 +21,18 @@ namespace PolyTraderSharp // Kopierte Master-Trader (TraderId -> TrackedTrader) public ConcurrentDictionary Traders { get; } = new(); + // Copytrading-Detail-Einstellungen je Account (AccountId -> Settings). + public ConcurrentDictionary AccountSettings { get; } = new(); + + /// + /// Liefert die Copytrading-Einstellungen für einen Account. Legt bei Bedarf einen + /// Default-Eintrag an, damit der Hot-Path nie null erhält. + /// + public CopyTradingAccountSettings GetAccountSettings(int accountId) + { + return AccountSettings.GetOrAdd(accountId, id => new CopyTradingAccountSettings { AccountId = id }); + } + private int _totalCopyTrades = 0; public int TotalCopyTrades { diff --git a/src/PolyTrader.Modules.CopyTrading/Models/CopyTradingAccountSettings.cs b/src/PolyTrader.Modules.CopyTrading/Models/CopyTradingAccountSettings.cs new file mode 100644 index 0000000..71a71bd --- /dev/null +++ b/src/PolyTrader.Modules.CopyTrading/Models/CopyTradingAccountSettings.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; + +namespace PolyTraderSharp.Models +{ + /// + /// Copytrading-spezifische Detail-Einstellungen je Account (Investment-/Zeit-Limits). + /// Bewusst getrennt vom Core- (allgemeine Account-Daten): + /// Diese Werte gehören dem Copytrading-Modul und werden im Modul-View bearbeitet. + /// Persistiert in der Collection "ct_account_settings" (BsonId = AccountId). + /// + public class CopyTradingAccountSettings + { + [Browsable(false)] + [MongoDB.Bson.Serialization.Attributes.BsonId] + public int AccountId { get; set; } + + [Category("01. Risk Management")] + public decimal PerMarketLimit { get; set; } = 5.0m; + + [Category("01. Risk Management")] + public decimal MaxPriceDifference { get; set; } = 2.0m; + + [Category("01. Risk Management")] + public decimal MaxBuyPrice { get; set; } = 0.98m; + + [Category("01. Risk Management")] + public decimal ProfitTarget { get; set; } = 50.0m; + + [Category("01. Risk Management")] + public decimal PreRedeemLimit { get; set; } = 0.0m; + + [Category("01. Risk Management")] + public decimal PerMasterLimit { get; set; } = 10.0m; + + [Category("02. Time Limits")] + public decimal perMaxTime6h { get; set; } = 20.0m; + + [Category("02. Time Limits")] + public decimal perMaxTime24h { get; set; } = 20.0m; + + [Category("02. Time Limits")] + public decimal perMaxTime72h { get; set; } = 20.0m; + + [Category("02. Time Limits")] + public decimal perMaxTimeNone { get; set; } = 40.0m; + } +} diff --git a/src/PolyTrader.Modules.CopyTrading/Persistence/ICopyTradingAccountSettingsRepository.cs b/src/PolyTrader.Modules.CopyTrading/Persistence/ICopyTradingAccountSettingsRepository.cs new file mode 100644 index 0000000..ebe118b --- /dev/null +++ b/src/PolyTrader.Modules.CopyTrading/Persistence/ICopyTradingAccountSettingsRepository.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using PolyTraderSharp.Models; + +namespace PolyTrader.Modules.CopyTrading.Persistence +{ + /// + /// Persistenz der copytrading-spezifischen Account-Detail-Einstellungen + /// (Collection "ct_account_settings", Schlüssel = AccountId). + /// + public interface ICopyTradingAccountSettingsRepository + { + List GetAll(); + CopyTradingAccountSettings? Get(int accountId); + void Upsert(CopyTradingAccountSettings settings); + void Delete(int accountId); + } +} diff --git a/src/PolyTrader.Modules.CopyTrading/Persistence/MongoCopyTradingAccountSettingsRepository.cs b/src/PolyTrader.Modules.CopyTrading/Persistence/MongoCopyTradingAccountSettingsRepository.cs new file mode 100644 index 0000000..44ffcdf --- /dev/null +++ b/src/PolyTrader.Modules.CopyTrading/Persistence/MongoCopyTradingAccountSettingsRepository.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using MongoDB.Driver; +using PolyTraderSharp.Models; + +namespace PolyTrader.Modules.CopyTrading.Persistence +{ + public class MongoCopyTradingAccountSettingsRepository : ICopyTradingAccountSettingsRepository + { + private readonly IMongoCollection _col; + + public MongoCopyTradingAccountSettingsRepository(IMongoDatabase db) + { + _col = db.GetCollection("ct_account_settings"); + } + + public List GetAll() => _col.Find(_ => true).ToList(); + + public CopyTradingAccountSettings? Get(int accountId) => + _col.Find(x => x.AccountId == accountId).FirstOrDefault(); + + public void Upsert(CopyTradingAccountSettings settings) => + _col.ReplaceOne(x => x.AccountId == settings.AccountId, settings, new ReplaceOptions { IsUpsert = true }); + + public void Delete(int accountId) => _col.DeleteOne(x => x.AccountId == accountId); + } +} diff --git a/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs b/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs index 11464a3..bd7ac8a 100644 --- a/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs +++ b/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs @@ -210,6 +210,9 @@ namespace PolyTraderSharp.Services private async Task ProcessAccountOrderAsync(AccountState account, TrackedTrader? trader, CopySignal signal, bool isNegRisk) { + // Copytrading-Detail-Einstellungen (Limits) dieses Accounts. + var settings = _copyState.GetAccountSettings(account.AccountId); + var mode = account.IsDemo ? _state.DemoTradingMode : _state.LiveTradingMode; if (mode == TradingMode.Inactive) { @@ -270,11 +273,11 @@ namespace PolyTraderSharp.Services if (signal.Side == "BUY") { - if (signal.Price > account.MaxBuyPrice) + if (signal.Price > settings.MaxBuyPrice) { _logger.TradeReasoning($"❌ Trade BUY [{signal.MarketQuestion}] [{shareType}] verworfen (Risk Limit):\n" + $" Konto: {account.Name}\n" + - $" Begründung: Preis (${signal.Price:F3}) übersteigt das MaxBuy Limit (${account.MaxBuyPrice:F3})"); + $" Begründung: Preis (${signal.Price:F3}) übersteigt das MaxBuy Limit (${settings.MaxBuyPrice:F3})"); return; } @@ -282,7 +285,7 @@ namespace PolyTraderSharp.Services decimal investedInMarket = activePositions.FirstOrDefault(p => p.TokenId == signal.TokenId)?.AmountUsd ?? 0m; decimal minTrade = 1.0m; - decimal maxAllowed = account.TotalBalance * (account.PerMarketLimit / 100.0m); + decimal maxAllowed = account.TotalBalance * (settings.PerMarketLimit / 100.0m); // Low Balance Bypass (Stufen-System) ALWAYS APPLIES if (account.TotalBalance < 150m) maxAllowed = Math.Min(1.20m, Math.Max(account.AvailableBalance, 0m)); @@ -298,9 +301,9 @@ namespace PolyTraderSharp.Services } else { - desiredLimitForSix = signal.Price * (1.0m + account.MaxPriceDifference / 100.0m); + desiredLimitForSix = signal.Price * (1.0m + settings.MaxPriceDifference / 100.0m); } - decimal orderPriceForSix = Math.Min(desiredLimitForSix, account.MaxBuyPrice); + decimal orderPriceForSix = Math.Min(desiredLimitForSix, settings.MaxBuyPrice); if (orderPriceForSix > 0.99m) orderPriceForSix = 0.99m; decimal costSix = 6m * orderPriceForSix; @@ -314,14 +317,14 @@ namespace PolyTraderSharp.Services decimal investedInMaster = trader != null ? activePositions.Where(p => p.SourceTraderId == trader.Id).Sum(p => (decimal)p.AmountUsd) : 0m; - decimal maxAllowedPerMaster = account.TotalBalance * (account.PerMasterLimit / 100.0m); + decimal maxAllowedPerMaster = account.TotalBalance * (settings.PerMasterLimit / 100.0m); if (trader != null && (investedInMaster + maxAmountToBuy) > maxAllowedPerMaster) { decimal pctInvested = account.TotalBalance > 0 ? (investedInMaster / account.TotalBalance) * 100m : 0m; _logger.TradeReasoning($"❌ Trade BUY [{signal.MarketQuestion}] [{shareType}] verworfen:\n" + $" Konto: {account.Name}\n" + - $" Begründung: PerMasterLimit ({account.PerMasterLimit:F1}%) erreicht. Bisher investiert in '{trader.DisplayName}': ${investedInMaster:F2} ({pctInvested:F1}%)."); + $" Begründung: PerMasterLimit ({settings.PerMasterLimit:F1}%) erreicht. Bisher investiert in '{trader.DisplayName}': ${investedInMaster:F2} ({pctInvested:F1}%)."); return; } @@ -335,25 +338,25 @@ namespace PolyTraderSharp.Services if (hoursLeft < 6) { - applicableTimeLimitPct = account.perMaxTime6h; + applicableTimeLimitPct = settings.perMaxTime6h; timeframeLabel = "< 6h"; investedInTimeframe = openVals.Where(p => p.ExpiryDate.HasValue && (p.ExpiryDate.Value - DateTime.UtcNow).TotalHours < 6).Sum(p => (decimal)p.AmountUsd); } else if (hoursLeft < 24) { - applicableTimeLimitPct = account.perMaxTime24h; + applicableTimeLimitPct = settings.perMaxTime24h; timeframeLabel = "< 24h"; investedInTimeframe = openVals.Where(p => p.ExpiryDate.HasValue && (p.ExpiryDate.Value - DateTime.UtcNow).TotalHours >= 6 && (p.ExpiryDate.Value - DateTime.UtcNow).TotalHours < 24).Sum(p => (decimal)p.AmountUsd); } else if (hoursLeft < 72) { - applicableTimeLimitPct = account.perMaxTime72h; + applicableTimeLimitPct = settings.perMaxTime72h; timeframeLabel = "< 72h"; investedInTimeframe = openVals.Where(p => p.ExpiryDate.HasValue && (p.ExpiryDate.Value - DateTime.UtcNow).TotalHours >= 24 && (p.ExpiryDate.Value - DateTime.UtcNow).TotalHours < 72).Sum(p => (decimal)p.AmountUsd); } else { - applicableTimeLimitPct = account.perMaxTimeNone; + applicableTimeLimitPct = settings.perMaxTimeNone; timeframeLabel = "> 72h"; investedInTimeframe = openVals.Where(p => !p.ExpiryDate.HasValue || (p.ExpiryDate.Value - DateTime.UtcNow).TotalHours >= 72).Sum(p => (decimal)p.AmountUsd); } @@ -401,10 +404,10 @@ namespace PolyTraderSharp.Services else { // Normaler Trader: prozentuales Limit aus Slave-Account Settings - desiredLimit = signal.Price * (1.0m + account.MaxPriceDifference / 100.0m); + desiredLimit = signal.Price * (1.0m + settings.MaxPriceDifference / 100.0m); } - orderPrice = Math.Min(desiredLimit, account.MaxBuyPrice); + orderPrice = Math.Min(desiredLimit, settings.MaxBuyPrice); if (orderPrice > 0.99m) orderPrice = 0.99m; var exact = PolymarketClobClient.CalculateExactOrderAmounts(maxAmountToBuy, orderPrice, orderPrice, "BUY"); diff --git a/src/PolyTrader.Modules.CopyTrading/Services/TraderMonitorService.cs b/src/PolyTrader.Modules.CopyTrading/Services/TraderMonitorService.cs index 19ca230..3dcd9b6 100644 --- a/src/PolyTrader.Modules.CopyTrading/Services/TraderMonitorService.cs +++ b/src/PolyTrader.Modules.CopyTrading/Services/TraderMonitorService.cs @@ -450,7 +450,7 @@ namespace PolyTraderSharp.Services try { - decimal expectedFillPrice = acc.PreRedeemLimit; + decimal expectedFillPrice = _copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit; decimal amountUsdc = Math.Max(pos.Size * expectedFillPrice, 0.01m); var result = await _clob.PlaceOrderAsync(acc, pos.TokenId, "SELL", amountUsdc, expectedFillPrice, "GTC", false, false); @@ -703,7 +703,7 @@ namespace PolyTraderSharp.Services try { _positionRepo.UpsertLive(acc.AccountId, existing); } catch { } // Auto-Redeem Fallback via REST - if (acc.PreRedeemLimit > 0 && curPrice >= acc.PreRedeemLimit && acc.IsActive) + if (_copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit > 0 && curPrice >= _copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit && acc.IsActive) { string redeemKey = $"{acc.AccountId}_{asset}"; bool allowAttempt = true; @@ -718,7 +718,7 @@ namespace PolyTraderSharp.Services { if (!acc.IsDemo && _state.LiveTradingMode == TradingMode.Active) { - _logger.Trade($"🚨 [REST AUTO REDEEM] {acc.Name} | {existing.MarketQuestion} | Preis >= {acc.PreRedeemLimit}"); + _logger.Trade($"🚨 [REST AUTO REDEEM] {acc.Name} | {existing.MarketQuestion} | Preis >= {_copyState.GetAccountSettings(acc.AccountId).PreRedeemLimit}"); _ = Task.Run(async () => await ExecuteRestAutoRedeemLive(acc, existing)); } }