Files
RichardandClaude Opus 4.8 b951b912f4 Phase 6 (Stufe 6): MongoDB vollstaendig entfernt
- Legacy-Form stillgelegt: frm_main(.Designer/.resx) + frm_analytics(.Designer/.resx)
  geloescht, 'Open Legacy'-Menue aus LauncherForm entfernt.
- Program.cs: IMongoDatabase-Registrierung + closed_trades-Cleanup weg;
  Trade-Nummerierung liest jetzt hoechste TradeId aus ICopyTradeLogRepository.
- StartupHydrationService: Mongo-/Bson-Fallback entfernt (Settings kommen aus
  dem Repo; fehlende Accounts erhalten Default-Settings).
- Alle Mongo*Repository (Core 4 + Modul 4) geloescht; DI-Zweige auf EF/MySQL
  reduziert (AddCorePersistence, CopyTradingModule).
- ConfigMigrator: Mongo-Pfad (Run/MigrateSettings) entfernt; nur noch
  RunFromJson + VerifyMySql (kein Mongo-Treiber mehr).
- Modelle: [BsonId] entfernt (EF-Keys via HasKey); ObjectId-Defaults -> Guid.
- LiteDB-Shim (MongoDbLiteDBShim) geloescht; MongoDB.Driver- + LiteDB-Pakete
  aus App/Core entfernt; stale usings + user-sichtbare Strings bereinigt.
- DatabaseOptions/appsettings.json: Mongo-Provider/Connection-Felder entfernt.
- Build gruen; --verify-mysql bestaetigt 3 Accounts/3 Settings/32 Trader/2344 Markets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:34:17 +02:00

173 lines
6.9 KiB
C#

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PolyTrader.Core.Persistence;
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.");
}
}
}