diff --git a/README.md b/README.md index beb7ae0..7f4a87d 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,9 @@ ticker, share count, share basis, etc. | holdingAccountID | string | true | true | The account hosting the position. | | holdingSecurityID | string | true | true | The security of the position. | | holdingLotID | string | true | true | The lot of the position, if any. | -| shareCount | double | false | false | The number of shares held in the position. | -| shareBasis | double | false | false | The price paid per share of the security. | -| acquiredAt | date | false | false | The date of the acquisition. | +| shareCount | double | false | true | The number of shares held in the position. | +| shareBasis | double | false | true | The price paid per share of the security. | +| acquiredAt | date | false | true | The date of the acquisition. | ### MSecurity @@ -319,6 +319,8 @@ public protocol AllocAttributable { Swift open-source libraries (by the same author): * [FINporter](https://github.com/openalloc/FINporter) - library and command-line tool to transform various specialized finance-related formats to the standardized schema of AllocData +* [SwiftTabler](https://github.com/openalloc/SwiftTabler) - multi-platform SwiftUI component for displaying (and interacting with) tabular data +* [SwiftDetailer](https://github.com/openalloc/SwiftDetailer) - multi-platform SwiftUI component for editing fielded data * [SwiftCompactor](https://github.com/openalloc/SwiftCompactor) - formatters for the concise display of Numbers, Currency, and Time Intervals * [SwiftModifiedDietz](https://github.com/openalloc/SwiftModifiedDietz) - A tool for calculating portfolio performance using the Modified Dietz method * [SwiftNiceScale](https://github.com/openalloc/SwiftNiceScale) - generate 'nice' numbers for label ticks over a range, such as for y-axis on a chart diff --git a/Sources/Model/Holding/MHolding+Attributes.swift b/Sources/Model/Holding/MHolding+Attributes.swift index 310cac5..75d1825 100644 --- a/Sources/Model/Holding/MHolding+Attributes.swift +++ b/Sources/Model/Holding/MHolding+Attributes.swift @@ -22,8 +22,8 @@ extension MHolding: AllocAttributable { AllocAttribute(CodingKeys.accountID, .string, isRequired: true, isKey: true, "The account hosting the position."), AllocAttribute(CodingKeys.securityID, .string, isRequired: true, isKey: true, "The security of the position."), AllocAttribute(CodingKeys.lotID, .string, isRequired: true, isKey: true, "The lot of the position, if any."), - AllocAttribute(CodingKeys.shareCount, .double, isRequired: false, isKey: false, "The number of shares held in the position."), - AllocAttribute(CodingKeys.shareBasis, .double, isRequired: false, isKey: false, "The price paid per share of the security."), - AllocAttribute(CodingKeys.acquiredAt, .date, isRequired: false, isKey: false, "The date of the acquisition."), + AllocAttribute(CodingKeys.shareCount, .double, isRequired: false, isKey: true, "The number of shares held in the position."), + AllocAttribute(CodingKeys.shareBasis, .double, isRequired: false, isKey: true, "The price paid per share of the security."), + AllocAttribute(CodingKeys.acquiredAt, .date, isRequired: false, isKey: true, "The date of the acquisition."), ] } diff --git a/Sources/Model/Holding/MHolding+Key.swift b/Sources/Model/Holding/MHolding+Key.swift index 990de0f..acb76b7 100644 --- a/Sources/Model/Holding/MHolding+Key.swift +++ b/Sources/Model/Holding/MHolding+Key.swift @@ -26,17 +26,31 @@ extension MHolding: AllocKeyed { public let accountNormID: NormalizedID public let securityNormID: NormalizedID public let lotNormID: NormalizedID + public var shareCount: Double? + public var shareBasis: Double? + public var acquiredAt: Date? - public init(accountID: String, securityID: String, lotID: String) { + public init(accountID: String, + securityID: String, + lotID: String, + shareCount: Double? = nil, + shareBasis: Double? = nil, + acquiredAt: Date? = nil) { self.accountNormID = MHolding.normalizeID(accountID) self.securityNormID = MHolding.normalizeID(securityID) self.lotNormID = MHolding.normalizeID(lotID) + self.shareCount = shareCount + self.shareBasis = shareBasis + self.acquiredAt = acquiredAt } public init(_ element: MHolding) { self.init(accountID: element.accountID, securityID: element.securityID, - lotID: element.lotID) + lotID: element.lotID, + shareCount: element.shareCount, + shareBasis: element.shareBasis, + acquiredAt: element.acquiredAt) } } @@ -45,6 +59,6 @@ extension MHolding: AllocKeyed { } public static var emptyKey: Key { - Key(accountID: "", securityID: "", lotID: "") + Key(accountID: "", securityID: "", lotID: "", shareCount: nil, shareBasis: nil, acquiredAt: Date.init(timeIntervalSinceReferenceDate: 0)) } } diff --git a/Sources/Model/Holding/MHolding+Row.swift b/Sources/Model/Holding/MHolding+Row.swift index 55bcce3..b07ebc2 100644 --- a/Sources/Model/Holding/MHolding+Row.swift +++ b/Sources/Model/Holding/MHolding+Row.swift @@ -44,7 +44,15 @@ extension MHolding: AllocRowed { let _securityID = getStr(row, CodingKeys.securityID.rawValue) else { throw AllocDataError.invalidPrimaryKey("Holding") } let _lotID = getStr(row, CodingKeys.lotID.rawValue) ?? "" - return Key(accountID: _accountID, securityID: _securityID, lotID: _lotID) + let _shareCount = getDouble(row, CodingKeys.shareCount.rawValue) + let _shareBasis = getDouble(row, CodingKeys.shareBasis.rawValue) + let _acquiredAt = getDate(row, CodingKeys.acquiredAt.rawValue) + return Key(accountID: _accountID, + securityID: _securityID, + lotID: _lotID, + shareCount: _shareCount, + shareBasis: _shareBasis, + acquiredAt: _acquiredAt) } public static func decode(_ rawRows: [RawRow], rejectedRows: inout [RawRow]) throws -> [DecodedRow] { diff --git a/Tests/Model/MHoldingTests.swift b/Tests/Model/MHoldingTests.swift index 25e8114..be021d4 100644 --- a/Tests/Model/MHoldingTests.swift +++ b/Tests/Model/MHoldingTests.swift @@ -72,16 +72,30 @@ class MHoldingTests: XCTestCase { } func testPrimaryKey() throws { - let element = MHolding(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ") + let element = MHolding(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ", shareCount: 5, shareBasis: 6, acquiredAt: timestamp) let actual = element.primaryKey - let expected = MHolding.Key(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ") + let expected = MHolding.Key(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ", shareCount: 5, shareBasis: 6, acquiredAt: timestamp) XCTAssertEqual(expected, actual) } - func testGetPrimaryKey() throws { - let finRow: MHolding.DecodedRow = ["holdingAccountID": " A-x?3 ", "holdingSecurityID": " -3B ! ", "holdingLotID": " "] + func testGetPrimaryKeyWithOptionals() throws { + let finRow: MHolding.DecodedRow = ["holdingAccountID": " A-x?3 ", + "holdingSecurityID": " -3B ! ", + "holdingLotID": " ", + "shareCount": 5, + "shareBasis": 6, + "acquiredAt": timestamp] let actual = try MHolding.getPrimaryKey(finRow) - let expected = MHolding.Key(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ") + let expected = MHolding.Key(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ", shareCount: 5, shareBasis: 6, acquiredAt: timestamp) + XCTAssertEqual(expected, actual) + } + + func testGetPrimaryKeyWithoutOptionals() throws { + let finRow: MHolding.DecodedRow = ["holdingAccountID": " A-x?3 ", + "holdingSecurityID": " -3B ! ", + "holdingLotID": " "] + let actual = try MHolding.getPrimaryKey(finRow) + let expected = MHolding.Key(accountID: " A-x?3 ", securityID: " -3B ! ", lotID: " ", shareCount: nil, shareBasis: nil, acquiredAt: nil) XCTAssertEqual(expected, actual) }