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,82 @@
using IBKRTrader.Core.Database;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Settings;
using IBKRTrader.Modules.CongressTrading.Database;
using IBKRTrader.Modules.CongressTrading.Scraper;
using IBKRTrader.Core.Workers;
namespace IBKRTrader.Modules.CongressTrading.Workers;
/// <summary>
/// Periodischer Scrape-Worker: alle 30 Minuten.
/// Lädt nur Seite 1 (neueste 96 Trades) und prüft auf neue Einträge.
/// Alle Daten (Datum, Typ, Value, Ticker, Mitglied) kommen direkt aus dem RSC-JSON.
/// </summary>
public class CongressScrapeWorker : WorkerBase
{
private readonly CapitolTradesScraper _scraper;
private readonly CongressRepository _repo;
public override string Name => "CT-ScrapeWorker";
public override string Module => "CT";
public override WorkerType Type => WorkerType.Worker;
protected override TimeSpan? Interval => TimeSpan.FromMinutes(30);
public CongressScrapeWorker(
LoggingService logger,
DatabaseService db,
CapitolTradesScraper scraper,
CongressRepository repo,
SettingsService settings)
: base(logger, db)
{
_scraper = scraper;
_repo = repo;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
Logger.Info(Module, "Suche neue Kongress-Trades (Seite 1)...");
var knownMembers = await _repo.GetAllMemberBioIdsAsync();
var (trades, _) = await _scraper.ScrapeTradesPageAsync(1, ct);
if (trades.Count == 0)
{
Logger.Warn(Module, "Scrape: Keine Trades auf Seite 1 gefunden.");
Info.Info = $"Letzter Lauf {DateTime.Now:HH:mm}: 0 Trades";
return;
}
// Neue Kongressmitglieder
var newMembers = 0;
foreach (var trade in trades.Where(t => t.MemberSnapshot != null))
{
var snapshot = trade.MemberSnapshot!;
if (knownMembers.Contains(snapshot.BioId)) continue;
await _repo.UpsertMemberAsync(snapshot);
knownMembers.Add(snapshot.BioId);
Logger.Info(Module, $"Neues Mitglied: {snapshot.Name} ({snapshot.BioId})");
newMembers++;
}
// Neue Trades
var newTrades = 0;
foreach (var trade in trades)
{
ct.ThrowIfCancellationRequested();
if (await _repo.TradeExistsAsync(trade.TradeId)) continue;
await _repo.InsertTradeAsync(trade, detailsFetched: true);
Logger.Info(Module,
$" + {trade.IssuerName} ({trade.TradeType}) [{trade.Ticker}] von {trade.MemberBioId}");
newTrades++;
}
var summary = $"{newTrades} neue Trades, {newMembers} neue Mitglieder";
Logger.Info(Module, $"Scrape abgeschlossen: {summary}");
Info.Info = $"Letzter Lauf {DateTime.Now:HH:mm}: {summary}";
}
}