.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>
153 lines
7.0 KiB
C#
153 lines
7.0 KiB
C#
using Dapper;
|
|
using IBKRTrader.Core.Database;
|
|
using IBKRTrader.Core.Logging;
|
|
|
|
namespace IBKRTrader.Core.IBKR;
|
|
|
|
/// <summary>
|
|
/// Datenzugriffsschicht für die core_ibkr_xxx-Tabellen.
|
|
/// Alle IBKR-Marktdaten-Operationen laufen über diese Klasse.
|
|
/// </summary>
|
|
public class IBKRMarketDataRepository
|
|
{
|
|
private readonly DatabaseService _db;
|
|
private readonly LoggingService _logger;
|
|
|
|
public IBKRMarketDataRepository(DatabaseService db, LoggingService logger)
|
|
{
|
|
_db = db;
|
|
_logger = logger;
|
|
}
|
|
|
|
// ─── Instruments ─────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Legt ein neues Instrument an oder aktualisiert ein bestehendes (UPSERT via conid).
|
|
/// Gibt die Instrument-ID zurück.
|
|
/// </summary>
|
|
public async Task<long> UpsertInstrumentAsync(IBKRInstrument instr)
|
|
{
|
|
const string sql = @"
|
|
INSERT INTO `core_ibkr_instruments`
|
|
(`ibkr_conid`, `symbol`, `sec_type`, `exchange`, `primary_exchange`,
|
|
`currency`, `company_name`, `isin`, `sector`, `industry`,
|
|
`description`, `active`, `last_fetched`)
|
|
VALUES
|
|
(@IbkrConid, @Symbol, @SecType, @Exchange, @PrimaryExchange,
|
|
@Currency, @CompanyName, @Isin, @Sector, @Industry,
|
|
@Description, @Active, @LastFetched)
|
|
ON DUPLICATE KEY UPDATE
|
|
`symbol` = VALUES(`symbol`),
|
|
`sec_type` = VALUES(`sec_type`),
|
|
`exchange` = VALUES(`exchange`),
|
|
`primary_exchange` = VALUES(`primary_exchange`),
|
|
`currency` = VALUES(`currency`),
|
|
`company_name` = VALUES(`company_name`),
|
|
`isin` = VALUES(`isin`),
|
|
`sector` = VALUES(`sector`),
|
|
`industry` = VALUES(`industry`),
|
|
`description` = VALUES(`description`),
|
|
`last_fetched` = VALUES(`last_fetched`);
|
|
SELECT `id` FROM `core_ibkr_instruments` WHERE `ibkr_conid` = @IbkrConid;";
|
|
|
|
await using var conn = _db.CreateConnection();
|
|
return await conn.ExecuteScalarAsync<long>(sql, instr);
|
|
}
|
|
|
|
/// <summary>Gibt alle aktiven Instrumente zurück.</summary>
|
|
public Task<IEnumerable<IBKRInstrument>> GetAllActiveInstrumentsAsync()
|
|
=> _db.QueryAsync<IBKRInstrument>(
|
|
"SELECT * FROM `core_ibkr_instruments` WHERE `active` = 1 ORDER BY `symbol`");
|
|
|
|
/// <summary>Findet ein Instrument anhand seiner IBKR ConID.</summary>
|
|
public Task<IBKRInstrument?> GetInstrumentByConidAsync(long conid)
|
|
=> _db.QueryFirstOrDefaultAsync<IBKRInstrument>(
|
|
"SELECT * FROM `core_ibkr_instruments` WHERE `ibkr_conid` = @conid",
|
|
new { conid });
|
|
|
|
/// <summary>Gibt die Anzahl aktiver Instrumente zurück.</summary>
|
|
public Task<int> GetActiveInstrumentCountAsync()
|
|
=> _db.ExecuteScalarAsync<int>(
|
|
"SELECT COUNT(*) FROM `core_ibkr_instruments` WHERE `active` = 1");
|
|
|
|
// ─── Market Data ─────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Fügt Marktdaten-Balken via UPSERT ein (ON DUPLICATE KEY UPDATE).
|
|
/// </summary>
|
|
public async Task UpsertMarketDataBatchAsync(IEnumerable<IBKRMarketBar> bars)
|
|
{
|
|
const string sql = @"
|
|
INSERT INTO `core_ibkr_market_data`
|
|
(`instrument_id`, `bar_size`, `timestamp`,
|
|
`open`, `high`, `low`, `close`, `volume`, `wap`, `bar_count`)
|
|
VALUES
|
|
(@InstrumentId, @BarSize, @Timestamp,
|
|
@Open, @High, @Low, @Close, @Volume, @Wap, @BarCount)
|
|
ON DUPLICATE KEY UPDATE
|
|
`open` = VALUES(`open`),
|
|
`high` = VALUES(`high`),
|
|
`low` = VALUES(`low`),
|
|
`close` = VALUES(`close`),
|
|
`volume` = VALUES(`volume`),
|
|
`wap` = VALUES(`wap`),
|
|
`bar_count` = VALUES(`bar_count`)";
|
|
|
|
await using var conn = _db.CreateConnection();
|
|
await conn.OpenAsync();
|
|
await conn.ExecuteAsync(sql, bars);
|
|
}
|
|
|
|
/// <summary>Gibt den neuesten Timestamp für ein Instrument zurück.</summary>
|
|
public Task<DateTime?> GetLatestBarTimestampAsync(long instrumentId, string barSize = "daily")
|
|
=> _db.QueryFirstOrDefaultAsync<DateTime?>(
|
|
@"SELECT MAX(`timestamp`) FROM `core_ibkr_market_data`
|
|
WHERE `instrument_id` = @instrumentId AND `bar_size` = @barSize",
|
|
new { instrumentId, barSize });
|
|
|
|
/// <summary>Gibt die Anzahl Bars für ein Instrument zurück.</summary>
|
|
public Task<int> GetBarCountAsync(long instrumentId, string barSize = "daily")
|
|
=> _db.ExecuteScalarAsync<int>(
|
|
@"SELECT COUNT(*) FROM `core_ibkr_market_data`
|
|
WHERE `instrument_id` = @instrumentId AND `bar_size` = @barSize",
|
|
new { instrumentId, barSize });
|
|
|
|
// ─── External Identifiers ────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Erstellt oder ignoriert ein External-Identifier-Mapping (IGNORE bei Duplikat).
|
|
/// </summary>
|
|
public Task UpsertExternalIdentifierAsync(long instrumentId, string source, string ticker)
|
|
=> _db.ExecuteAsync(@"
|
|
INSERT IGNORE INTO `core_ibkr_external_identifiers`
|
|
(`instrument_id`, `source`, `ticker`)
|
|
VALUES (@instrumentId, @source, @ticker)",
|
|
new { instrumentId, source, ticker });
|
|
|
|
/// <summary>
|
|
/// Findet ein Instrument anhand eines externen Tickers (z.B. aus ct_trade).
|
|
/// </summary>
|
|
public Task<IBKRInstrument?> FindInstrumentByExternalTickerAsync(string source, string ticker)
|
|
=> _db.QueryFirstOrDefaultAsync<IBKRInstrument>(@"
|
|
SELECT i.* FROM `core_ibkr_instruments` i
|
|
INNER JOIN `core_ibkr_external_identifiers` e ON e.`instrument_id` = i.`id`
|
|
WHERE e.`source` = @source AND e.`ticker` = @ticker
|
|
LIMIT 1",
|
|
new { source, ticker });
|
|
|
|
/// <summary>
|
|
/// Findet alle einzigartigen Ticker aus ct_trade, die noch kein IBKR-Mapping haben.
|
|
/// </summary>
|
|
public Task<IEnumerable<string>> GetUnmappedTickersFromCongressTradesAsync()
|
|
=> _db.QueryAsync<string>(@"
|
|
SELECT DISTINCT t.`ticker`
|
|
FROM `ct_trade` t
|
|
WHERE t.`ticker` IS NOT NULL
|
|
AND t.`ticker` != ''
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM `core_ibkr_external_identifiers` e
|
|
WHERE e.`source` = 'capitoltrades' AND e.`ticker` = t.`ticker`
|
|
)
|
|
ORDER BY t.`ticker`");
|
|
}
|