- 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>
173 lines
6.9 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|