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:
@@ -0,0 +1,274 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using IBKRTrader.Core.Logging;
|
||||
using IBKRTrader.Core.Settings;
|
||||
|
||||
namespace IBKRTrader.Core.IBKR;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP-Client für die IBKR Client Portal Web API (Gateway).
|
||||
/// Alle API-Aufrufe laufen über den lokalen Client Portal Gateway
|
||||
/// (Standard: https://localhost:5000/v1/api).
|
||||
///
|
||||
/// Voraussetzungen:
|
||||
/// - Client Portal Gateway muss laufen (Java-Prozess)
|
||||
/// - Session muss per Browser + 2FA authentifiziert sein
|
||||
/// </summary>
|
||||
public class IBKRGatewayService
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
private readonly LoggingService _logger;
|
||||
private readonly HttpClient _http;
|
||||
private readonly SemaphoreSlim _rateLimiter;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public IBKRGatewayService(SettingsService settings, LoggingService logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
|
||||
// HttpClient mit SSL-Bypass für localhost (Gateway hat kein signiertes Zertifikat)
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
||||
};
|
||||
|
||||
var apiSettings = _settings.Settings.IBKRWebApi;
|
||||
_http = new HttpClient(handler)
|
||||
{
|
||||
BaseAddress = new Uri(apiSettings.GatewayUrl.TrimEnd('/') + "/v1/api/"),
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
|
||||
// Standard-Header setzen
|
||||
_http.DefaultRequestHeaders.Add("User-Agent", "IBKRTrader/1.0");
|
||||
_http.DefaultRequestHeaders.Add("Accept", "*/*");
|
||||
|
||||
// Rate-Limiter: maximal N gleichzeitige Anfragen (wird seriell mit Delay verwendet)
|
||||
_rateLimiter = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
// ─── Rate Limiting ───────────────────────────────────────────────────────
|
||||
|
||||
private async Task RateLimitAsync(CancellationToken ct)
|
||||
{
|
||||
await _rateLimiter.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
var delayMs = 1000 / Math.Max(1, _settings.Settings.IBKRWebApi.MaxRequestsPerSecond);
|
||||
await Task.Delay(delayMs, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rateLimiter.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Session / Auth ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Prüft den Authentifizierungsstatus der Gateway-Session.
|
||||
/// </summary>
|
||||
public async Task<IBKRAuthStatus?> CheckAuthStatusAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
var response = await _http.PostAsync("iserver/auth/status", null, ct);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.Warn("IBKR", $"Auth-Status-Check fehlgeschlagen: HTTP {(int)response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<IBKRAuthStatus>(JsonOpts, ct);
|
||||
IsConnected = result?.Authenticated == true;
|
||||
return result;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Warn("IBKR", $"Gateway nicht erreichbar: {ex.Message}");
|
||||
IsConnected = false;
|
||||
return null;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hält die Session am Leben (Ping). Sollte alle ~60 Sekunden aufgerufen werden.
|
||||
/// </summary>
|
||||
public async Task TickleAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
await _http.PostAsync("tickle", null, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("IBKR", $"Tickle fehlgeschlagen: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert die Brokerage-Session über den Gateway.
|
||||
/// </summary>
|
||||
public async Task<bool> InitBrokerageSessionAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
var body = new StringContent("{\"publish\":true,\"compete\":true}",
|
||||
System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _http.PostAsync("iserver/auth/ssodh/init", body, ct);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("IBKR", $"Brokerage-Session-Init fehlgeschlagen: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Contract Search ─────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Sucht einen Contract anhand des Symbols.
|
||||
/// POST /iserver/secdef/search
|
||||
/// Body: {"symbol":"AAPL"}
|
||||
/// </summary>
|
||||
public async Task<List<IBKRContractSearchResult>?> SearchContractBySymbolAsync(
|
||||
string symbol, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
var body = JsonContent.Create(new { symbol });
|
||||
var response = await _http.PostAsync("iserver/secdef/search", body, ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync(ct);
|
||||
_logger.Warn("IBKR", $"Contract-Suche für '{symbol}' fehlgeschlagen: HTTP {(int)response.StatusCode} – {errorBody}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<List<IBKRContractSearchResult>>(JsonOpts, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("IBKR", $"Contract-Suche für '{symbol}' fehlgeschlagen: {ex.Message}", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ruft detaillierte Contract-Informationen ab.
|
||||
/// GET /iserver/contract/{conid}/info
|
||||
/// </summary>
|
||||
public async Task<IBKRContractInfo?> GetContractInfoAsync(
|
||||
long conid, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
var response = await _http.GetAsync($"iserver/contract/{conid}/info", ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.Warn("IBKR", $"Contract-Info für conid {conid} fehlgeschlagen: HTTP {(int)response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<IBKRContractInfo>(JsonOpts, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("IBKR", $"Contract-Info für conid {conid} fehlgeschlagen: {ex.Message}", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht Aktien-Contracts nach Symbolen.
|
||||
/// GET /trsrv/stocks?symbols=AAPL,MSFT
|
||||
/// </summary>
|
||||
public async Task<Dictionary<string, List<IBKRStockContract>>?> SearchStocksBySymbolAsync(
|
||||
string symbols, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
var response = await _http.GetAsync($"trsrv/stocks?symbols={Uri.EscapeDataString(symbols)}", ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.Warn("IBKR", $"Stock-Suche fehlgeschlagen: HTTP {(int)response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Dictionary<string, List<IBKRStockContract>>>(JsonOpts, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("IBKR", $"Stock-Suche fehlgeschlagen: {ex.Message}", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Historical Market Data ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Ruft historische Marktdaten ab.
|
||||
/// GET /iserver/marketdata/history?conid={conid}&period={period}&bar={bar}
|
||||
///
|
||||
/// period: 1d, 1w, 1m, 3m, 6m, 1y, 2y, 5y
|
||||
/// bar: 1min, 5min, 15min, 30min, 1h, 1d, 1w, 1m
|
||||
/// </summary>
|
||||
public async Task<IBKRHistoricalDataResponse?> GetHistoricalDataAsync(
|
||||
long conid, string period = "2y", string bar = "1d",
|
||||
bool outsideRth = false, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RateLimitAsync(ct);
|
||||
var url = $"iserver/marketdata/history?conid={conid}" +
|
||||
$"&period={Uri.EscapeDataString(period)}" +
|
||||
$"&bar={Uri.EscapeDataString(bar)}" +
|
||||
$"&outsideRth={outsideRth.ToString().ToLower()}";
|
||||
|
||||
var response = await _http.GetAsync(url, ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync(ct);
|
||||
_logger.Warn("IBKR", $"Historische Daten für conid {conid} fehlgeschlagen: HTTP {(int)response.StatusCode} – {errorBody}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<IBKRHistoricalDataResponse>(JsonOpts, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("IBKR", $"Historische Daten für conid {conid} fehlgeschlagen: {ex.Message}", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Disconnect ──────────────────────────────────────────────────────────
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
IsConnected = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
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`");
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace IBKRTrader.Core.IBKR;
|
||||
|
||||
// ─── DB Entities ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Repräsentiert ein IBKR-Instrument in core_ibkr_instruments.
|
||||
/// ibkr_conid ist der zentrale, eindeutige IBKR-Schlüssel.
|
||||
/// </summary>
|
||||
public class IBKRInstrument
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long IbkrConid { get; set; }
|
||||
public string Symbol { get; set; } = "";
|
||||
public string SecType { get; set; } = "STK";
|
||||
public string Exchange { get; set; } = "SMART";
|
||||
public string? PrimaryExchange { get; set; }
|
||||
public string Currency { get; set; } = "USD";
|
||||
public string? CompanyName { get; set; }
|
||||
public string? Isin { get; set; }
|
||||
public string? Sector { get; set; }
|
||||
public string? Industry { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public bool Active { get; set; } = true;
|
||||
public DateTime? LastFetched { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repräsentiert einen OHLCV-Balken in core_ibkr_market_data.
|
||||
/// </summary>
|
||||
public class IBKRMarketBar
|
||||
{
|
||||
public long InstrumentId { get; set; }
|
||||
public string BarSize { get; set; } = "daily";
|
||||
public DateTime Timestamp { get; set; }
|
||||
public decimal Open { get; set; }
|
||||
public decimal High { get; set; }
|
||||
public decimal Low { get; set; }
|
||||
public decimal Close { get; set; }
|
||||
public long Volume { get; set; }
|
||||
public decimal? Wap { get; set; }
|
||||
public int? BarCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Externes Ticker-Mapping in core_ibkr_external_identifiers.
|
||||
/// </summary>
|
||||
public class IBKRExternalIdentifier
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long InstrumentId { get; set; }
|
||||
public string Source { get; set; } = "";
|
||||
public string Ticker { get; set; } = "";
|
||||
}
|
||||
|
||||
// ─── API Response DTOs ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Response von GET /iserver/auth/status
|
||||
/// </summary>
|
||||
public class IBKRAuthStatus
|
||||
{
|
||||
[JsonPropertyName("authenticated")]
|
||||
public bool Authenticated { get; set; }
|
||||
|
||||
[JsonPropertyName("competing")]
|
||||
public bool Competing { get; set; }
|
||||
|
||||
[JsonPropertyName("connected")]
|
||||
public bool Connected { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[JsonPropertyName("fail")]
|
||||
public string? Fail { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Einzelnes Ergebnis der Contract-Suche (POST /iserver/secdef/search).
|
||||
/// </summary>
|
||||
public class IBKRContractSearchResult
|
||||
{
|
||||
[JsonPropertyName("conid")]
|
||||
public long ConId { get; set; }
|
||||
|
||||
[JsonPropertyName("companyHeader")]
|
||||
public string? CompanyHeader { get; set; }
|
||||
|
||||
[JsonPropertyName("companyName")]
|
||||
public string? CompanyName { get; set; }
|
||||
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonPropertyName("restricted")]
|
||||
public string? Restricted { get; set; }
|
||||
|
||||
[JsonPropertyName("fop")]
|
||||
public string? Fop { get; set; }
|
||||
|
||||
[JsonPropertyName("opt")]
|
||||
public string? Opt { get; set; }
|
||||
|
||||
[JsonPropertyName("war")]
|
||||
public string? War { get; set; }
|
||||
|
||||
[JsonPropertyName("sections")]
|
||||
public List<IBKRContractSection>? Sections { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sections innerhalb eines Contract-Suchergebnisses.
|
||||
/// </summary>
|
||||
public class IBKRContractSection
|
||||
{
|
||||
[JsonPropertyName("secType")]
|
||||
public string? SecType { get; set; }
|
||||
|
||||
[JsonPropertyName("months")]
|
||||
public string? Months { get; set; }
|
||||
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("exchange")]
|
||||
public string? Exchange { get; set; }
|
||||
|
||||
[JsonPropertyName("legStr")]
|
||||
public string? LegStr { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response von GET /iserver/contract/{conid}/info
|
||||
/// </summary>
|
||||
public class IBKRContractInfo
|
||||
{
|
||||
[JsonPropertyName("cfi_code")]
|
||||
public string? CfiCode { get; set; }
|
||||
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("cusip")]
|
||||
public string? Cusip { get; set; }
|
||||
|
||||
[JsonPropertyName("expiry_full")]
|
||||
public string? ExpiryFull { get; set; }
|
||||
|
||||
[JsonPropertyName("con_id")]
|
||||
public long ConId { get; set; }
|
||||
|
||||
[JsonPropertyName("maturity_date")]
|
||||
public string? MaturityDate { get; set; }
|
||||
|
||||
[JsonPropertyName("industry")]
|
||||
public string? Industry { get; set; }
|
||||
|
||||
[JsonPropertyName("instrument_type")]
|
||||
public string? InstrumentType { get; set; }
|
||||
|
||||
[JsonPropertyName("trading_class")]
|
||||
public string? TradingClass { get; set; }
|
||||
|
||||
[JsonPropertyName("valid_exchanges")]
|
||||
public string? ValidExchanges { get; set; }
|
||||
|
||||
[JsonPropertyName("allow_sell_long")]
|
||||
public bool? AllowSellLong { get; set; }
|
||||
|
||||
[JsonPropertyName("is_zero_commission_security")]
|
||||
public bool? IsZeroCommissionSecurity { get; set; }
|
||||
|
||||
[JsonPropertyName("local_symbol")]
|
||||
public string? LocalSymbol { get; set; }
|
||||
|
||||
[JsonPropertyName("classifier")]
|
||||
public string? Classifier { get; set; }
|
||||
|
||||
[JsonPropertyName("currency")]
|
||||
public string? Currency { get; set; }
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string? Text { get; set; }
|
||||
|
||||
[JsonPropertyName("underlying_con_id")]
|
||||
public long? UnderlyingConId { get; set; }
|
||||
|
||||
[JsonPropertyName("r_t_h")]
|
||||
public bool? Rth { get; set; }
|
||||
|
||||
[JsonPropertyName("company_name")]
|
||||
public string? CompanyName { get; set; }
|
||||
|
||||
[JsonPropertyName("smart_available")]
|
||||
public bool? SmartAvailable { get; set; }
|
||||
|
||||
[JsonPropertyName("exchange")]
|
||||
public string? Exchange { get; set; }
|
||||
|
||||
[JsonPropertyName("listing_exchange")]
|
||||
public string? ListingExchange { get; set; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string? Category { get; set; }
|
||||
|
||||
[JsonPropertyName("sector")]
|
||||
public string? Sector { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response von GET /iserver/marketdata/history
|
||||
/// </summary>
|
||||
public class IBKRHistoricalDataResponse
|
||||
{
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string? Text { get; set; }
|
||||
|
||||
[JsonPropertyName("priceFactor")]
|
||||
public int? PriceFactor { get; set; }
|
||||
|
||||
[JsonPropertyName("startTime")]
|
||||
public string? StartTime { get; set; }
|
||||
|
||||
[JsonPropertyName("high")]
|
||||
public string? High { get; set; }
|
||||
|
||||
[JsonPropertyName("low")]
|
||||
public string? Low { get; set; }
|
||||
|
||||
[JsonPropertyName("timePeriod")]
|
||||
public string? TimePeriod { get; set; }
|
||||
|
||||
[JsonPropertyName("barLength")]
|
||||
public int? BarLength { get; set; }
|
||||
|
||||
[JsonPropertyName("mdAvailability")]
|
||||
public string? MdAvailability { get; set; }
|
||||
|
||||
[JsonPropertyName("mktDataDelay")]
|
||||
public int? MktDataDelay { get; set; }
|
||||
|
||||
[JsonPropertyName("outsideRth")]
|
||||
public bool? OutsideRth { get; set; }
|
||||
|
||||
[JsonPropertyName("tradingDayDuration")]
|
||||
public int? TradingDayDuration { get; set; }
|
||||
|
||||
[JsonPropertyName("volumeFactor")]
|
||||
public int? VolumeFactor { get; set; }
|
||||
|
||||
[JsonPropertyName("priceDisplayRule")]
|
||||
public int? PriceDisplayRule { get; set; }
|
||||
|
||||
[JsonPropertyName("priceDisplayValue")]
|
||||
public string? PriceDisplayValue { get; set; }
|
||||
|
||||
[JsonPropertyName("negativeCapable")]
|
||||
public bool? NegativeCapable { get; set; }
|
||||
|
||||
[JsonPropertyName("messageVersion")]
|
||||
public int? MessageVersion { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public List<IBKRHistoricalBar>? Data { get; set; }
|
||||
|
||||
[JsonPropertyName("points")]
|
||||
public int? Points { get; set; }
|
||||
|
||||
[JsonPropertyName("travelTime")]
|
||||
public int? TravelTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Einzelner OHLCV-Balken aus der IBKR Historical Data Response.
|
||||
/// </summary>
|
||||
public class IBKRHistoricalBar
|
||||
{
|
||||
[JsonPropertyName("o")]
|
||||
public decimal Open { get; set; }
|
||||
|
||||
[JsonPropertyName("c")]
|
||||
public decimal Close { get; set; }
|
||||
|
||||
[JsonPropertyName("h")]
|
||||
public decimal High { get; set; }
|
||||
|
||||
[JsonPropertyName("l")]
|
||||
public decimal Low { get; set; }
|
||||
|
||||
[JsonPropertyName("v")]
|
||||
public long Volume { get; set; }
|
||||
|
||||
[JsonPropertyName("t")]
|
||||
public long Timestamp { get; set; } // Unix timestamp in ms
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response von GET /trsrv/stocks?symbols=...
|
||||
/// Die API gibt ein Dictionary zurück: { "AAPL": [{ ... }] }
|
||||
/// </summary>
|
||||
public class IBKRStockContract
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("chineseName")]
|
||||
public string? ChineseName { get; set; }
|
||||
|
||||
[JsonPropertyName("assetClass")]
|
||||
public string? AssetClass { get; set; }
|
||||
|
||||
[JsonPropertyName("contracts")]
|
||||
public List<IBKRStockContractEntry>? Contracts { get; set; }
|
||||
}
|
||||
|
||||
public class IBKRStockContractEntry
|
||||
{
|
||||
[JsonPropertyName("conid")]
|
||||
public long ConId { get; set; }
|
||||
|
||||
[JsonPropertyName("exchange")]
|
||||
public string? Exchange { get; set; }
|
||||
|
||||
[JsonPropertyName("isUS")]
|
||||
public bool? IsUS { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user