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>
This commit is contained in:
Richard
2026-07-26 18:19:47 +02:00
co-authored by Claude Opus 4.8
commit ebeb035e92
47 changed files with 4527 additions and 0 deletions
@@ -0,0 +1,133 @@
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.");
}
}