Files
IBKRTrader/Core/Workers/BuiltIn/IBKRInstrumentSyncWorker.cs
T
RichardandClaude Opus 4.8 ebeb035e92 Initial commit: IBKRTrader
.NET WinForms-Anwendung (Core, Modules/CongressTrading, UI).
Enthaelt .gitignore und settings.example.json als Konfigurationsvorlage.
Echte settings.json mit Zugangsdaten ist bewusst ausgeschlossen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 18:19:47 +02:00

134 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using IBKRTrader.Core.Database;
using IBKRTrader.Core.IBKR;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Settings;
namespace IBKRTrader.Core.Workers.BuiltIn;
/// <summary>
/// Synchronisiert IBKR-Instrumentenstammdaten alle 30 Tage.
/// </summary>
public class IBKRInstrumentSyncWorker : WorkerBase
{
public override string Name => "IBKR Instrument Sync";
public override string Module => "Core";
public override WorkerType Type => WorkerType.Worker;
protected override TimeSpan? Interval =>
TimeSpan.FromMinutes(_settings.Settings.WorkerSettings.InstrumentSyncWorker.IntervalMinutes);
private readonly SettingsService _settings;
private readonly IBKRGatewayService _gateway;
private readonly IBKRMarketDataRepository _repo;
public IBKRInstrumentSyncWorker(
LoggingService logger, DatabaseService db,
SettingsService settings, IBKRGatewayService gateway,
IBKRMarketDataRepository repo)
: base(logger, db)
{
_settings = settings;
_gateway = gateway;
_repo = repo;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
if (!_settings.Settings.IBKRWebApi.Enabled)
{
Logger.Info(Module, "IBKR Web API deaktiviert InstrumentSync übersprungen.");
return;
}
var authStatus = await _gateway.CheckAuthStatusAsync(ct);
if (authStatus?.Authenticated != true)
{
Logger.Warn(Module, "IBKR Gateway nicht authentifiziert InstrumentSync übersprungen.");
return;
}
await _gateway.TickleAsync(ct);
await MapUnmappedTickersAsync(ct);
await RefreshExistingInstrumentsAsync(ct);
}
private async Task MapUnmappedTickersAsync(CancellationToken ct)
{
var unmapped = (await _repo.GetUnmappedTickersFromCongressTradesAsync()).ToList();
if (unmapped.Count == 0) { Logger.Info(Module, "Keine neuen unmapped Ticker."); return; }
Logger.Info(Module, $"Mappe {unmapped.Count} neue Ticker auf IBKR-Contracts...");
int mapped = 0, failed = 0;
foreach (var ticker in unmapped)
{
if (ct.IsCancellationRequested) break;
try
{
var results = await _gateway.SearchContractBySymbolAsync(ticker, ct);
if (results == null || results.Count == 0)
{ Logger.Warn(Module, $"Kein Contract für: {ticker}"); failed++; continue; }
long conid = results[0].ConId;
var existing = await _repo.GetInstrumentByConidAsync(conid);
if (existing == null)
{
var info = await _gateway.GetContractInfoAsync(conid, ct);
var instrument = new IBKRInstrument
{
IbkrConid = conid, Symbol = info?.Symbol ?? ticker,
SecType = info?.InstrumentType ?? "STK",
Exchange = info?.Exchange ?? "SMART",
PrimaryExchange = info?.ListingExchange,
Currency = info?.Currency ?? "USD",
CompanyName = info?.CompanyName ?? results[0].CompanyName,
Sector = info?.Sector, Industry = info?.Industry,
Description = info?.Text, Active = true,
LastFetched = DateTime.UtcNow
};
var id = await _repo.UpsertInstrumentAsync(instrument);
await _repo.UpsertExternalIdentifierAsync(id, "capitoltrades", ticker);
Logger.Info(Module, $"Neu: {ticker} → conid {conid}");
}
else
{
await _repo.UpsertExternalIdentifierAsync(existing.Id, "capitoltrades", ticker);
}
mapped++;
}
catch (Exception ex) { Logger.Error(Module, $"Mapping '{ticker}': {ex.Message}", ex); failed++; }
}
Logger.Info(Module, $"Mapping: {mapped} OK, {failed} Fehler.");
}
private async Task RefreshExistingInstrumentsAsync(CancellationToken ct)
{
var instruments = (await _repo.GetAllActiveInstrumentsAsync()).ToList();
if (instruments.Count == 0) return;
Logger.Info(Module, $"Aktualisiere {instruments.Count} Instrumente...");
int ok = 0, err = 0;
foreach (var instr in instruments)
{
if (ct.IsCancellationRequested) break;
try
{
var info = await _gateway.GetContractInfoAsync(instr.IbkrConid, ct);
if (info == null) { err++; continue; }
instr.Symbol = info.Symbol ?? instr.Symbol;
instr.CompanyName = info.CompanyName ?? instr.CompanyName;
instr.Sector = info.Sector ?? instr.Sector;
instr.Industry = info.Industry ?? instr.Industry;
instr.LastFetched = DateTime.UtcNow;
await _repo.UpsertInstrumentAsync(instr);
ok++;
}
catch (Exception ex) { Logger.Error(Module, $"Update {instr.Symbol}: {ex.Message}", ex); err++; }
}
Logger.Info(Module, $"Stammdaten: {ok} OK, {err} Fehler.");
}
}