handle stock split
Some checks failed
Generate check / check-changes (pull_request) Successful in 3s
Quality / check-changes (pull_request) Successful in 3s
Generate check / verify-generate (pull_request) Successful in 16s
Quality / run-tests (pull_request) Failing after 10s

This commit is contained in:
2026-05-16 15:34:24 +01:00
parent 5cdccfcdb1
commit 7cc5d1cf75
6 changed files with 258 additions and 35 deletions

View File

@@ -30,7 +30,7 @@ func TestRecordReader_ReadRecord(t *testing.T) {
r: bytes.NewBufferString(`Market buy,2025-07-03 10:44:29,XX1234567890,ABXY,"Aspargus Broccoli",EOF987654321,2.4387014200,7.3690000000,USD,1.17995999,,"EUR",15.25,"EUR",0.25,"EUR",0.02,"EUR",,`),
want: Record{
symbol: "XX1234567890",
side: internal.KindBuy,
kind: internal.KindBuy,
quantity: ShouldParseDecimal(t, "2.4387014200"),
price: ShouldParseDecimal(t, "7.3690000000"),
timestamp: time.Date(2025, 7, 3, 10, 44, 29, 0, time.UTC),
@@ -44,7 +44,7 @@ func TestRecordReader_ReadRecord(t *testing.T) {
r: bytes.NewBufferString(`Market sell,2025-08-04 11:45:30,XX1234567890,ABXY,"Aspargus Broccoli",EOF987654321,2.4387014200,7.9999999999,USD,1.17995999,,"EUR",15.25,"EUR",,,0.02,"EUR",0.1,"EUR"`),
want: Record{
symbol: "XX1234567890",
side: internal.KindSell,
kind: internal.KindSell,
quantity: ShouldParseDecimal(t, "2.4387014200"),
price: ShouldParseDecimal(t, "7.9999999999"),
timestamp: time.Date(2025, 8, 4, 11, 45, 30, 0, time.UTC),
@@ -123,8 +123,8 @@ func TestRecordReader_ReadRecord(t *testing.T) {
t.Fatalf("want symbol %v but got %v", tt.want.symbol, got.Symbol())
}
if got.Kind() != tt.want.side {
t.Fatalf("want side %v but got %v", tt.want.side, got.Kind())
if got.Kind() != tt.want.kind {
t.Fatalf("want side %v but got %v", tt.want.kind, got.Kind())
}
if got.Price().Cmp(tt.want.price) != 0 {
@@ -154,6 +154,70 @@ func TestRecordReader_ReadRecord(t *testing.T) {
}
}
func TestRecordReader_ReadRecord_Split(t *testing.T) {
// open row has the NEW (post-split) position: more shares at lower price
// close row has the OLD (pre-split) position: fewer shares at higher price
// ratio = openQty / closeQty = 0.5 / 0.1 = 5 (a 5:1 split)
splitOpen := `Stock split open,2025-06-03 05:34:16,XX1234567890,ABXY,"Aspargus Broccoli",EOF111111111,0.5000000000,20.0000000000,EUR,1.00000000,,,10.00,"EUR",,,,,,`
splitClose := `Stock split close,2025-06-03 05:34:16,XX1234567890,ABXY,"Aspargus Broccoli",EOF222222222,0.1000000000,100.0000000000,EUR,1.00000000,0.00,"EUR",10.00,"EUR",,,,,,`
t.Run("well-formed split pair returns split record with correct ratio", func(t *testing.T) {
rr := NewRecordReader(
bytes.NewBufferString(splitOpen+"\n"+splitClose),
NewFigiClientSecurityTypeStub(t, "Common Stock"),
)
got, err := rr.ReadRecord(t.Context())
if err != nil {
t.Fatalf("ReadRecord() failed: %v", err)
}
if got.Kind() != internal.KindSplit {
t.Errorf("want kind %v but got %v", internal.KindSplit, got.Kind())
}
if got.Symbol() != "NO0013536151" {
t.Errorf("want symbol NO0013536151 but got %v", got.Symbol())
}
wantTimestamp := time.Date(2025, 6, 3, 5, 34, 16, 0, time.UTC)
if !got.Timestamp().Equal(wantTimestamp) {
t.Errorf("want timestamp %v but got %v", wantTimestamp, got.Timestamp())
}
// ratio = openQty / closeQty = 0.1245045 / 0.0249009 ≈ 5
openQty := ShouldParseDecimal(t, "0.1245045000")
closeQty := ShouldParseDecimal(t, "0.0249009000")
wantRatio := openQty.Div(closeQty)
if !got.Quantity().Equal(wantRatio) {
t.Errorf("want ratio %v but got %v", wantRatio, got.Quantity())
}
})
t.Run("close without prior open errors", func(t *testing.T) {
rr := NewRecordReader(
bytes.NewBufferString(splitClose),
NewFigiClientSecurityTypeStub(t, "Common Stock"),
)
_, err := rr.ReadRecord(t.Context())
if err == nil {
t.Fatal("expected error but got none")
}
})
t.Run("two opens without close errors", func(t *testing.T) {
rr := NewRecordReader(
bytes.NewBufferString(splitOpen+"\n"+splitOpen),
NewFigiClientSecurityTypeStub(t, "Common Stock"),
)
_, err := rr.ReadRecord(t.Context())
if err == nil {
t.Fatal("expected error but got none")
}
})
}
func Test_figiNatureGetter(t *testing.T) {
tests := []struct {
name string // description of this test case