diff --git a/DataProcessing/QuiverInsiderTradingDataDownloader.cs b/DataProcessing/QuiverInsiderTradingDataDownloader.cs
index 291a282..b1f26ce 100644
--- a/DataProcessing/QuiverInsiderTradingDataDownloader.cs
+++ b/DataProcessing/QuiverInsiderTradingDataDownloader.cs
@@ -119,10 +119,13 @@ public bool Run(DateTime processDate)
var uploadedDate = insiderTrade.Uploaded.Value.Date;
// Omit fileDate when its calendar day matches uploaded. Reader falls back to uploadedDate,
// preserving the day but dropping intraday precision (acceptable trade-off for storage).
- var fileDate = insiderTrade.FileDate?.Date == uploadedDate
+ var fileDate = insiderTrade.FileDate == default || insiderTrade.FileDate.Date == uploadedDate
? string.Empty
- : insiderTrade.FileDate?.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) ?? string.Empty;
- var transactionDate = insiderTrade.Date?.ToString("yyyyMMdd", CultureInfo.InvariantCulture) ?? string.Empty;
+ : insiderTrade.FileDate.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture);
+ var transactionDate = insiderTrade.Date == default
+ ? string.Empty
+ : insiderTrade.Date.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
+ var name = SanitizeCsv(insiderTrade.Name);
var officerTitle = SanitizeCsv(insiderTrade.OfficerTitle);
var transactionCode = insiderTrade.TransactionCode.ToCsv();
var ownership = insiderTrade.DirectOrIndirectOwnership.ToCsv();
@@ -130,7 +133,7 @@ public bool Run(DateTime processDate)
var line = $"{uploadedDate:yyyyMMdd},{fileDate},{transactionDate}," +
$"{transactionCode},{insiderTrade.PricePerShare},{insiderTrade.Shares},{insiderTrade.SharesOwnedFollowing}," +
- $"{acquiredDisposed},{ownership},{officerTitle}," +
+ $"{acquiredDisposed},{ownership},{name},{officerTitle}," +
$"{insiderTrade.IsDirector.ToCsv()},{insiderTrade.IsOfficer.ToCsv()},{insiderTrade.IsTenPercentOwner.ToCsv()},{insiderTrade.IsOther.ToCsv()}";
foreach (var rawTicker in tickerList)
diff --git a/QuiverInsiderTrading.cs b/QuiverInsiderTrading.cs
index 6d1ab07..3574ead 100644
--- a/QuiverInsiderTrading.cs
+++ b/QuiverInsiderTrading.cs
@@ -38,13 +38,13 @@ public class QuiverInsiderTrading : BaseDataCollection
///
[JsonProperty(PropertyName = "Date")]
[JsonConverter(typeof(DateTimeJsonConverter), "yyyy-MM-dd")]
- public DateTime? Date { get; set; }
+ public DateTime Date { get; set; }
///
/// Time the transaction was filed and became publicly available
///
[JsonProperty(PropertyName = "fileDate")]
- public DateTime? FileDate { get; set; }
+ public DateTime FileDate { get; set; }
///
/// Type of transaction (see SEC Form 4 codes:
@@ -83,6 +83,12 @@ public class QuiverInsiderTrading : BaseDataCollection
[JsonProperty(PropertyName = "directOrIndirectOwnership")]
public OwnershipType DirectOrIndirectOwnership { get; set; }
+ ///
+ /// Name of the transactor
+ ///
+ [JsonProperty(PropertyName = "Name")]
+ public string Name { get; set; }
+
///
/// Corporate title of the transactor
///
@@ -159,18 +165,19 @@ public override BaseData Reader(SubscriptionDataConfig config, string line, Date
Time = uploadedDate.AddDays(-1),
Symbol = config.Symbol,
FileDate = (csv[1].IfNotNullOrEmpty(s => Parse.DateTimeExact(s, "yyyyMMddHHmmss")) ?? uploadedDate).AddDays(-1),
- Date = csv[2].IfNotNullOrEmpty(s => Parse.DateTimeExact(s, "yyyyMMdd")),
+ Date = csv[2].IfNotNullOrEmpty(s => Parse.DateTimeExact(s, "yyyyMMdd")) ?? uploadedDate.AddDays(-1),
TransactionCode = QuiverQuantCsvExtensions.ToTransactionCode(csv[3]),
PricePerShare = csv[4].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)),
Shares = csv[5].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)),
SharesOwnedFollowing = csv[6].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)),
AcquiredDisposedCode = QuiverQuantCsvExtensions.ToAcquiredDisposedCode(csv[7]),
DirectOrIndirectOwnership = QuiverQuantCsvExtensions.ToOwnershipType(csv[8]),
- OfficerTitle = csv[9],
- IsDirector = QuiverQuantCsvExtensions.ToNullableBool(csv[10]),
- IsOfficer = QuiverQuantCsvExtensions.ToNullableBool(csv[11]),
- IsTenPercentOwner = QuiverQuantCsvExtensions.ToNullableBool(csv[12]),
- IsOther = QuiverQuantCsvExtensions.ToNullableBool(csv[13]),
+ Name = csv[9],
+ OfficerTitle = csv[10],
+ IsDirector = QuiverQuantCsvExtensions.ToNullableBool(csv[11]),
+ IsOfficer = QuiverQuantCsvExtensions.ToNullableBool(csv[12]),
+ IsTenPercentOwner = QuiverQuantCsvExtensions.ToNullableBool(csv[13]),
+ IsOther = QuiverQuantCsvExtensions.ToNullableBool(csv[14]),
};
}
@@ -184,7 +191,7 @@ public override string ToString()
// we are the wrapper instance
return $"{Symbol} - Data Points {Data.Count}";
}
- return $"{Symbol} ({OfficerTitle}) - {TransactionCode}/{AcquiredDisposedCode} - " +
+ return $"{Symbol} ({Name}, {OfficerTitle}) - {TransactionCode}/{AcquiredDisposedCode} - " +
$"{Shares} @ {PricePerShare} - SharesOwnedFollowing: {SharesOwnedFollowing} - " +
$"Ownership: {DirectOrIndirectOwnership} - Date: {Date} - Filed: {FileDate}";
}
@@ -213,6 +220,7 @@ public override BaseData Clone()
SharesOwnedFollowing = SharesOwnedFollowing,
AcquiredDisposedCode = AcquiredDisposedCode,
DirectOrIndirectOwnership = DirectOrIndirectOwnership,
+ Name = Name,
OfficerTitle = OfficerTitle,
IsDirector = IsDirector,
IsOfficer = IsOfficer,
diff --git a/QuiverInsiderTradingUniverse.cs b/QuiverInsiderTradingUniverse.cs
index 57e8228..49c5460 100644
--- a/QuiverInsiderTradingUniverse.cs
+++ b/QuiverInsiderTradingUniverse.cs
@@ -34,12 +34,12 @@ public class QuiverInsiderTradingUniverse : BaseDataCollection
///
/// Transaction date as reported on SEC Form 4
///
- public DateTime? Date { get; set; }
+ public DateTime Date { get; set; }
///
/// Time the transaction was filed and became publicly available
///
- public DateTime? FileDate { get; set; }
+ public DateTime FileDate { get; set; }
///
/// Type of transaction (SEC Form 4 code)
@@ -71,6 +71,11 @@ public class QuiverInsiderTradingUniverse : BaseDataCollection
///
public OwnershipType DirectOrIndirectOwnership { get; set; }
+ ///
+ /// Name of the transactor
+ ///
+ public string Name { get; set; }
+
///
/// Corporate title of the transactor
///
@@ -143,18 +148,19 @@ public override BaseData Reader(SubscriptionDataConfig config, string line, Date
Time = date.AddDays(-1),
Symbol = new Symbol(SecurityIdentifier.Parse(csv[0]), csv[1]),
FileDate = (csv[2].IfNotNullOrEmpty(s => Parse.DateTimeExact(s, "yyyyMMddHHmmss")) ?? date).AddDays(-1),
- Date = csv[3].IfNotNullOrEmpty(s => Parse.DateTimeExact(s, "yyyyMMdd")),
+ Date = csv[3].IfNotNullOrEmpty(s => Parse.DateTimeExact(s, "yyyyMMdd")) ?? date.AddDays(-1),
TransactionCode = QuiverQuantCsvExtensions.ToTransactionCode(csv[4]),
PricePerShare = price,
Shares = csv[6].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)),
SharesOwnedFollowing = csv[7].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)),
AcquiredDisposedCode = QuiverQuantCsvExtensions.ToAcquiredDisposedCode(csv[8]),
DirectOrIndirectOwnership = QuiverQuantCsvExtensions.ToOwnershipType(csv[9]),
- OfficerTitle = csv[10],
- IsDirector = QuiverQuantCsvExtensions.ToNullableBool(csv[11]),
- IsOfficer = QuiverQuantCsvExtensions.ToNullableBool(csv[12]),
- IsTenPercentOwner = QuiverQuantCsvExtensions.ToNullableBool(csv[13]),
- IsOther = QuiverQuantCsvExtensions.ToNullableBool(csv[14]),
+ Name = csv[10],
+ OfficerTitle = csv[11],
+ IsDirector = QuiverQuantCsvExtensions.ToNullableBool(csv[12]),
+ IsOfficer = QuiverQuantCsvExtensions.ToNullableBool(csv[13]),
+ IsTenPercentOwner = QuiverQuantCsvExtensions.ToNullableBool(csv[14]),
+ IsOther = QuiverQuantCsvExtensions.ToNullableBool(csv[15]),
Value = price ?? 0
};
}
@@ -173,6 +179,7 @@ public override string ToString()
Invariant($"SharesOwnedFollowing: {SharesOwnedFollowing} ") +
Invariant($"AcquiredDisposedCode: {AcquiredDisposedCode} ") +
Invariant($"DirectOrIndirectOwnership: {DirectOrIndirectOwnership} ") +
+ Invariant($"Name: {Name} ") +
Invariant($"OfficerTitle: {OfficerTitle} ") +
Invariant($"IsDirector: {IsDirector} ") +
Invariant($"IsOfficer: {IsOfficer} ") +
@@ -195,6 +202,7 @@ public override BaseData Clone()
SharesOwnedFollowing = SharesOwnedFollowing,
AcquiredDisposedCode = AcquiredDisposedCode,
DirectOrIndirectOwnership = DirectOrIndirectOwnership,
+ Name = Name,
OfficerTitle = OfficerTitle,
IsDirector = IsDirector,
IsOfficer = IsOfficer,
diff --git a/tests/QuiverInsiderTradingTests.cs b/tests/QuiverInsiderTradingTests.cs
index 104fa14..e162684 100644
--- a/tests/QuiverInsiderTradingTests.cs
+++ b/tests/QuiverInsiderTradingTests.cs
@@ -55,7 +55,7 @@ public void Reader_ParsesCompactFormat()
var symbol = new Symbol(SecurityIdentifier.Parse("AAPL R735QTJ8XC9X"), "AAPL");
var config = CreateConfig(symbol);
var factory = new QuiverInsiderTrading();
- var line = "20260508,20260507093000,20260507,P,150.25,100,500,A,D,CEO,T,T,F,";
+ var line = "20260508,20260507093000,20260507,P,150.25,100,500,A,D,John Smith,CEO,T,T,F,";
var result = (QuiverInsiderTrading)factory.Reader(config, line, new DateTime(2026, 5, 8), false);
@@ -70,6 +70,7 @@ public void Reader_ParsesCompactFormat()
Assert.AreEqual(500m, result.SharesOwnedFollowing);
Assert.AreEqual(AcquiredDisposedCode.Acquired, result.AcquiredDisposedCode);
Assert.AreEqual(OwnershipType.Direct, result.DirectOrIndirectOwnership);
+ Assert.AreEqual("John Smith", result.Name);
Assert.AreEqual("CEO", result.OfficerTitle);
Assert.AreEqual(true, result.IsDirector);
Assert.AreEqual(true, result.IsOfficer);
@@ -84,7 +85,7 @@ public void Reader_EmptyFileDateFallsBackToUploadedMinusOne()
var config = CreateConfig(symbol);
var factory = new QuiverInsiderTrading();
// csv[1] (fileDate) is empty — Reader uses uploadedDate.AddDays(-1)
- var line = "20260508,,20260507,S,275,1534,13366,D,D,CFO,,T,,";
+ var line = "20260508,,20260507,S,275,1534,13366,D,D,Jane Doe,CFO,,T,,";
var result = (QuiverInsiderTrading)factory.Reader(config, line, new DateTime(2026, 5, 8), false);
@@ -92,6 +93,21 @@ public void Reader_EmptyFileDateFallsBackToUploadedMinusOne()
Assert.AreEqual(new DateTime(2026, 5, 7), result.Time);
}
+ [Test]
+ public void Reader_EmptyDateFallsBackToUploadedMinusOne()
+ {
+ var symbol = new Symbol(SecurityIdentifier.Parse("AAPL R735QTJ8XC9X"), "AAPL");
+ var config = CreateConfig(symbol);
+ var factory = new QuiverInsiderTrading();
+ // csv[2] (Date) is empty — Reader falls back to uploadedDate.AddDays(-1)
+ var line = "20260508,,,,,1717,40879,,,Jane Doe,,,,,";
+
+ var result = (QuiverInsiderTrading)factory.Reader(config, line, new DateTime(2026, 5, 8), false);
+
+ Assert.AreEqual(new DateTime(2026, 5, 7), result.Date);
+ Assert.AreEqual(new DateTime(2026, 5, 7), result.FileDate);
+ }
+
[Test]
public void Reader_EmptyOptionalFieldsAreNull()
{
@@ -99,7 +115,7 @@ public void Reader_EmptyOptionalFieldsAreNull()
var config = CreateConfig(symbol);
var factory = new QuiverInsiderTrading();
// All optional numerics/booleans empty
- var line = "20260508,,20260507,M,,1717,40879,A,D,,,,,";
+ var line = "20260508,,20260507,M,,1717,40879,A,D,,,,,,";
var result = (QuiverInsiderTrading)factory.Reader(config, line, new DateTime(2026, 5, 8), false);
@@ -107,6 +123,7 @@ public void Reader_EmptyOptionalFieldsAreNull()
Assert.IsNull(result.PricePerShare);
Assert.AreEqual(1717m, result.Shares);
Assert.AreEqual(40879m, result.SharesOwnedFollowing);
+ Assert.AreEqual(string.Empty, result.Name);
Assert.AreEqual(string.Empty, result.OfficerTitle);
Assert.IsNull(result.IsDirector);
Assert.IsNull(result.IsOfficer);
@@ -119,7 +136,7 @@ public void UniverseReader_ParsesCompactFormat()
{
var factory = new QuiverInsiderTradingUniverse();
// csv[0]=sid, csv[1]=ticker, csv[2]=fileDate(empty -> fallback), csv[3]=Date, csv[4]=TransactionCode, ...
- var line = "AAPL R735QTJ8XC9X,AAPL,,20260507,P,150.25,100,500,A,D,CEO,T,T,F,";
+ var line = "AAPL R735QTJ8XC9X,AAPL,,20260507,P,150.25,100,500,A,D,John Smith,CEO,T,T,F,";
var result = (QuiverInsiderTradingUniverse)factory.Reader(null, line, new DateTime(2026, 5, 8), false);
@@ -131,6 +148,8 @@ public void UniverseReader_ParsesCompactFormat()
Assert.AreEqual(150.25m, result.PricePerShare);
Assert.AreEqual(AcquiredDisposedCode.Acquired, result.AcquiredDisposedCode);
Assert.AreEqual(OwnershipType.Direct, result.DirectOrIndirectOwnership);
+ Assert.AreEqual("John Smith", result.Name);
+ Assert.AreEqual("CEO", result.OfficerTitle);
Assert.AreEqual(150.25m, result.Value);
}
@@ -186,6 +205,7 @@ private BaseData CreateNewInstance()
SharesOwnedFollowing = 0.0m,
AcquiredDisposedCode = AcquiredDisposedCode.Acquired,
DirectOrIndirectOwnership = OwnershipType.Direct,
+ Name = "John Smith",
OfficerTitle = "CEO",
IsDirector = false,
IsOfficer = true,