Handelshistorie fasst Optionsschluessel; Order-ID landet in der Bindung
Zwei Maengel, sichtbar geworden beim Pruefen der vier Tabellen nach dem Order-Nachweis (#293). core_trade_history.Symbol war varchar(20) und traegt seit der Optionserweiterung den Instrumentenschluessel statt des Ticker-Symbols. "MSFT|20260918|P|302.5" sind 21 Zeichen - gekuerzt auf "MSFT|20260918|P|302." liest sich das als Strike 302. Ein still verfaelschter Eintrag in der Handelshistorie ist schlimmer als gar keiner. Jetzt 64 wie core_position.ContractKey. MarkSubmittedAsync rief niemand auf: im synchronen Pfad gibt es keinen beobachtbaren Zeitpunkt zwischen Absenden und Endstatus. BrokerOrderId blieb dadurch leer, und der Abgleich beim Neustart haette keine Bindung einer Order zuordnen koennen - genau die Aufgabe, fuer die die Spalte da ist. Die ID wird jetzt beim Abschluss festgehalten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -214,8 +214,9 @@ public sealed class CapitalService : ICapitalService
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SettleReservationAsync(
|
||||
long reservationId, int filledQuantity, decimal actualAmount, CancellationToken ct = default) =>
|
||||
_reservations.SettleAsync(reservationId, filledQuantity, actualAmount, ct);
|
||||
long reservationId, int filledQuantity, decimal actualAmount, string? brokerOrderId = null,
|
||||
CancellationToken ct = default) =>
|
||||
_reservations.SettleAsync(reservationId, filledQuantity, actualAmount, brokerOrderId, ct);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CancelReservationAsync(long reservationId, string reason, CancellationToken ct = default) =>
|
||||
|
||||
@@ -34,7 +34,8 @@ public interface ICapitalService
|
||||
/// (mit Puffer) und tatsächlichem Bedarf fließt zurück in den Pool.
|
||||
/// </summary>
|
||||
Task SettleReservationAsync(
|
||||
long reservationId, int filledQuantity, decimal actualAmount, CancellationToken ct = default);
|
||||
long reservationId, int filledQuantity, decimal actualAmount, string? brokerOrderId = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>Gibt eine Bindung frei, ohne dass ausgeführt wurde.</summary>
|
||||
Task CancelReservationAsync(long reservationId, string reason, CancellationToken ct = default);
|
||||
|
||||
@@ -189,7 +189,8 @@ public sealed class ReservationService
|
||||
/// <param name="filledQuantity">Tatsächlich ausgeführte Menge; 0 = nichts ausgeführt.</param>
|
||||
/// <param name="actualAmount">Tatsächlich benötigtes Kapital (bei Teilfüllung anteilig).</param>
|
||||
public async Task SettleAsync(
|
||||
long id, int filledQuantity, decimal actualAmount, CancellationToken ct = default)
|
||||
long id, int filledQuantity, decimal actualAmount, string? brokerOrderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await using var db = await _dbf.CreateDbContextAsync(ct);
|
||||
var row = await db.Reservations.FindAsync([id], ct);
|
||||
@@ -197,6 +198,12 @@ public sealed class ReservationService
|
||||
|
||||
row.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Die Order-ID spaetestens hier festhalten. Im synchronen Pfad gibt es keinen beobachtbaren
|
||||
// Zeitpunkt zwischen Absenden und Endstatus, an dem MarkSubmittedAsync greifen koennte -
|
||||
// ohne diese Zeile bliebe BrokerOrderId leer, und der Abgleich beim Neustart haette nichts,
|
||||
// woran er eine Bindung einer Order zuordnen koennte.
|
||||
if (!string.IsNullOrWhiteSpace(brokerOrderId)) row.BrokerOrderId = brokerOrderId;
|
||||
|
||||
if (filledQuantity <= 0)
|
||||
{
|
||||
row.State = ReservationState.Cancelled;
|
||||
|
||||
@@ -56,7 +56,12 @@ public class CoreDbContext : DbContext
|
||||
e.ToTable("core_trade_history");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Module).HasMaxLength(50);
|
||||
e.Property(x => x.Symbol).HasMaxLength(20);
|
||||
// 64 wie core_position.ContractKey: hier steht seit der Optionserweiterung der
|
||||
// Instrumentenschluessel und nicht mehr das nackte Ticker-Symbol. Bei 20 Zeichen wuerde
|
||||
// "MSFT|20260918|P|302.5" auf "MSFT|20260918|P|302." gekuerzt - und das laesst sich als
|
||||
// Strike 302 lesen. Ein still verfaelschter Eintrag in der Handelshistorie ist
|
||||
// schlimmer als gar keiner.
|
||||
e.Property(x => x.Symbol).HasMaxLength(64);
|
||||
e.Property(x => x.Action).HasMaxLength(10);
|
||||
e.Property(x => x.SignalId).HasMaxLength(64);
|
||||
e.Property(x => x.IbkrOrderId).HasMaxLength(100);
|
||||
|
||||
Generated
+598
@@ -0,0 +1,598 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using IBKRTrader.Core.Persistence.Ef;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IBKRTrader.Core.Persistence.Ef.Migrations
|
||||
{
|
||||
[DbContext(typeof(CoreDbContext))]
|
||||
[Migration("20260904134622_WidenTradeHistorySymbol")]
|
||||
partial class WidenTradeHistorySymbol
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.13")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.IBKR.IBKRExternalIdentifier", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<long>("InstrumentId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("Ticker")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Source", "Ticker");
|
||||
|
||||
b.HasIndex("InstrumentId", "Source", "Ticker")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("core_ibkr_external_identifiers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.IBKR.IBKRInstrument", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("CompanyName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("varchar(5)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Exchange")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<long>("IbkrConid")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Industry")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<string>("Isin")
|
||||
.HasMaxLength(12)
|
||||
.HasColumnType("varchar(12)");
|
||||
|
||||
b.Property<DateTime?>("LastFetched")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("PrimaryExchange")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("SecType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("Sector")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Active");
|
||||
|
||||
b.HasIndex("IbkrConid")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Symbol");
|
||||
|
||||
b.ToTable("core_ibkr_instruments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.IBKR.IBKRMarketBar", b =>
|
||||
{
|
||||
b.Property<long>("InstrumentId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("BarSize")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("BarCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Close")
|
||||
.HasPrecision(12, 6)
|
||||
.HasColumnType("decimal(12,6)");
|
||||
|
||||
b.Property<decimal>("High")
|
||||
.HasPrecision(12, 6)
|
||||
.HasColumnType("decimal(12,6)");
|
||||
|
||||
b.Property<decimal>("Low")
|
||||
.HasPrecision(12, 6)
|
||||
.HasColumnType("decimal(12,6)");
|
||||
|
||||
b.Property<decimal>("Open")
|
||||
.HasPrecision(12, 6)
|
||||
.HasColumnType("decimal(12,6)");
|
||||
|
||||
b.Property<long>("Volume")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal?>("Wap")
|
||||
.HasPrecision(12, 6)
|
||||
.HasColumnType("decimal(12,6)");
|
||||
|
||||
b.HasKey("InstrumentId", "BarSize", "Timestamp");
|
||||
|
||||
b.HasIndex("Timestamp");
|
||||
|
||||
b.ToTable("core_ibkr_market_data", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreBudget", b =>
|
||||
{
|
||||
b.Property<string>("Module")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<decimal>("MaxPerTrade")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("TotalBudget")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<decimal>("UsedBudget")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.HasKey("Module");
|
||||
|
||||
b.ToTable("core_budget", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreDecisionRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("ContextJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Decision")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Module")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<decimal>("SignalPrice")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Module", "Timestamp");
|
||||
|
||||
b.ToTable("core_decision_journal", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreOrderEvent", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("DetailsJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("Module")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("OrderType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Response")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Symbol", "Timestamp");
|
||||
|
||||
b.ToTable("core_order_events", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CorePosition", b =>
|
||||
{
|
||||
b.Property<string>("Module")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("ContractKey")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<decimal>("AvgPrice")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("varchar(5)");
|
||||
|
||||
b.Property<DateOnly?>("Expiry")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("Kind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<int>("Multiplier")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Right")
|
||||
.HasMaxLength(1)
|
||||
.HasColumnType("varchar(1)");
|
||||
|
||||
b.Property<decimal?>("Strike")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Module", "ContractKey");
|
||||
|
||||
b.HasIndex("Symbol");
|
||||
|
||||
b.ToTable("core_position", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreReservation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Book")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("BrokerOrderId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<string>("ContractKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("varchar(5)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Shares")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("State")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BrokerOrderId");
|
||||
|
||||
b.HasIndex("ExpiresAt");
|
||||
|
||||
b.HasIndex("SignalId", "ContractKey")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("State", "Book");
|
||||
|
||||
b.ToTable("core_reservation", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("core_settings", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreTrade", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Action")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("IbkrOrderId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<string>("Module")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<decimal>("Quantity")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<decimal>("TotalValue")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<DateTime>("TradedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Module");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Symbol");
|
||||
|
||||
b.ToTable("core_trade_history", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreWorkerLog", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime?>("FinishedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Module")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<DateTime?>("StartedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("WorkerName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("WorkerName", "StartedAt");
|
||||
|
||||
b.ToTable("core_worker_log", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IBKRTrader.Core.Persistence.Ef.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class WidenTradeHistorySymbol : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Symbol",
|
||||
table: "core_trade_history",
|
||||
type: "varchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(20)",
|
||||
oldMaxLength: 20)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Symbol",
|
||||
table: "core_trade_history",
|
||||
type: "varchar(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(64)",
|
||||
oldMaxLength: 64)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,8 +530,8 @@ namespace IBKRTrader.Core.Persistence.Ef.Migrations
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<decimal>("TotalValue")
|
||||
.HasPrecision(18, 4)
|
||||
|
||||
@@ -155,7 +155,8 @@ public sealed class ExecutionService : IExecutionService
|
||||
// 9. Bindung abschließen. Reserviert wurde mit Puffer, ausgeführt zum echten Preis – die
|
||||
// Differenz fließt zurück in den Pool.
|
||||
var actualAmount = result.FilledQuantity * result.AvgFillPrice * signal.Instrument.Multiplier;
|
||||
await _capital.SettleReservationAsync(reservationId, result.FilledQuantity, actualAmount, ct);
|
||||
await _capital.SettleReservationAsync(
|
||||
reservationId, result.FilledQuantity, actualAmount, result.OrderId, ct);
|
||||
|
||||
Journal(signal, TradeDecision.Executed, DecisionReason.OrderPlaced,
|
||||
$"Ausgeführt: {sideStr} {result.FilledQuantity}x {signal.Instrument.Key} @ {result.AvgFillPrice:F2}",
|
||||
|
||||
@@ -243,8 +243,9 @@ public class ExecutionServiceTests
|
||||
await CreateSut().ExecuteAsync(BuySignal);
|
||||
|
||||
// Reserviert wurde mit Puffer, ausgefuehrt zum echten Preis: 5 x 100 = 500 fliessen als
|
||||
// tatsaechlicher Bedarf zurueck, der Rest wird freigegeben.
|
||||
await _capital.Received(1).SettleReservationAsync(1, 5, 500m, Arg.Any<CancellationToken>());
|
||||
// tatsaechlicher Bedarf zurueck, der Rest wird freigegeben. Die Order-ID gehoert dazu -
|
||||
// ohne sie kann der Abgleich beim Neustart keine Bindung einer Order zuordnen.
|
||||
await _capital.Received(1).SettleReservationAsync(1, 5, 500m, "O1", Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user