Files
PolyTraderSharp/services/MarketSyncService.cs
T
bergmandClaude Opus 4.8 7197f9b31c Phase 3c: Unkritische Call-Sites auf Repositories umgestellt
- MarketSyncService: markets-Zugriffe -> IMarketRepository (kein _db mehr).
- PolymarketWssClient: demo_positions -> IPositionRepository,
  accounts -> IAccountRepository (closed_trades bleibt vorerst auf _db,
  ClosedTrade-Repo folgt in Phase 5).
- Build 0 Fehler.

Hot-Path (CopyTradingEngine, TraderMonitorService) und frm_main-UI bewusst
noch NICHT migriert (Schritt B / Phase 5).

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

175 lines
6.9 KiB
C#

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using PolyTrader.Core.Persistence;
using PolyTraderSharp.Extensions;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
namespace PolyTraderSharp.Services
{
public class MarketSyncService : BackgroundService
{
private readonly PolymarketApiService _apiService;
private readonly IMarketRepository _marketRepo;
private readonly TerminalLogger _logger;
private readonly JobStatusRow _jobStatus;
private readonly TradingState _state;
public MarketSyncService(PolymarketApiService apiService, IMarketRepository marketRepo, TerminalLogger logger, JobManager jobManager, TradingState state)
{
_apiService = apiService;
_marketRepo = marketRepo;
_logger = logger;
_state = state;
_jobStatus = new JobStatusRow
{
JobName = "Market Data Sync",
Description = "Polls Polymarket Gamma API for the 1000 newest markets.",
StatusText = "Pending Initial Delay..."
};
_jobStatus.ManualTriggerAction = async () =>
{
string oldStatus = _jobStatus.StatusText;
_jobStatus.StatusText = "Running (Manual)...";
await SyncMarketsAsync();
_jobStatus.StatusText = "Idle";
};
jobManager.RegisterJob(_jobStatus);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.Info("MarketSyncService started. Will sync markets every 1 hour.");
// Give the app some time to start up before initial sync
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
_jobStatus.StatusText = "Idle";
while (!stoppingToken.IsCancellationRequested)
{
if (_jobStatus.IsEnabled)
{
try
{
_jobStatus.StatusText = "Running (Scheduled)...";
await SyncMarketsAsync();
_jobStatus.LastRun = DateTime.Now;
}
catch (Exception ex)
{
_logger.Error($"MarketSyncService loop error: {ex.Message}");
_jobStatus.StatusText = "Error!";
}
finally
{
if (_jobStatus.StatusText != "Error!")
_jobStatus.StatusText = "Idle";
}
}
else
{
_jobStatus.StatusText = "Paused";
}
// Sleep for 3 minutes to keep the Cache extremely fresh against high-frequency listings
_jobStatus.NextRun = DateTime.Now.AddMinutes(3);
await Task.Delay(TimeSpan.FromMinutes(3), stoppingToken);
}
}
private async Task SyncMarketsAsync()
{
_logger.Info("Syncing newest markets from Polymarket API...");
var newMarkets = await _apiService.GetRecentMarketsAsync(1000);
if (newMarkets.Count == 0)
{
_logger.Warning("No markets returned from Polymarket API during sync.");
return;
}
_marketRepo.EnsureIndexes();
int inserted = 0;
int updated = 0;
foreach (var market in newMarkets)
{
var existing = _marketRepo.GetById(market.Id);
if (existing == null)
{
_marketRepo.Insert(market);
inserted++;
// NEW: Hot-Load active markets directly into RAM Cache
if (!market.Closed && !string.IsNullOrEmpty(market.ClobTokenIds))
{
try
{
var tokenIds = System.Text.Json.JsonSerializer.Deserialize<List<string>>(market.ClobTokenIds);
if (tokenIds != null)
{
foreach(var t in tokenIds)
{
_state.MarketCache[t] = market;
}
}
} catch { } // Ignore JSON parse error if malformed
}
}
else
{
// Update dynamic fields like EndDate, Active, Closed
existing.EndDate = market.EndDate;
existing.Active = market.Active;
existing.Closed = market.Closed;
existing.NegRisk = market.NegRisk;
// The API can sometimes be slow to assign ClobTokenIds. Update them if we got new ones.
if (string.IsNullOrEmpty(existing.ClobTokenIds) && !string.IsNullOrEmpty(market.ClobTokenIds))
{
existing.ClobTokenIds = market.ClobTokenIds;
}
_marketRepo.Update(existing);
updated++;
// Keep RAM cache synchronized to prevent using stale active/closed flags
if (!string.IsNullOrEmpty(existing.ClobTokenIds))
{
try
{
var tokenIds = System.Text.Json.JsonSerializer.Deserialize<List<string>>(existing.ClobTokenIds);
if (tokenIds != null)
{
foreach(var t in tokenIds)
{
if (!existing.Closed)
{
// Unconditionally keep active markets hot in the cache
_state.MarketCache[t] = existing;
}
else
{
// Only update if it is already there (e.g. to flag it as closed for running logic)
if (_state.MarketCache.ContainsKey(t))
_state.MarketCache[t] = existing;
}
}
}
} catch { } // Ignore JSON parse error if malformed
}
}
}
_logger.Info($"Market Sync Complete: {inserted} new markets, {updated} updated.");
}
}
}