Skip to content

Commit

Permalink
Use account number and importer type to identify wealthsimple accounts
Browse files Browse the repository at this point in the history
Fixes #45
  • Loading branch information
Nef10 committed Sep 17, 2021
1 parent 03675f9 commit 66a2297
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 18 deletions.
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ If the commodity in your ledger differs from the symbol used by Wealthsimple, si

### Accounts

For account you need to add two meta data entries:
For Wealthsimple accounts themselves, you need to add this metadata: `importer-type: "wealthsimple"` and `number: "XXX"`. If the account can hold more than one commodity (all accounts except chequing and saving), it needs to follow this structure: `Assets:X:Y:Z:CashAccountName`, `Assets:X:Y:Z:CommodityName`, `Assets:X:Y:Z:OtherCommodityName`. The name of the cash account does not matter, but all other account must end with the commodity symbol (see above). Only add the `importer-type` and `number` to the cash account.

For account used for transaction to and from your Wealthsimple accounts you need to add two meta data entries:
* First is the account type (`wealthsimple-account-type`), you can look up the possible values [here](https://github.com/Nef10/WealthsimpleDownloader/blob/main/Sources/Wealthsimple/Account.swift#L37)
* Second is a key (`wealthsimple-key`):
* For holdings and cash assset accounts this is the symbol of the stock, ETF or currency
* For dividend income accounts this is the symbol as well
* Second is a key (`wealthsimple-key`), for example:
* For dividend income accounts this is the symbol as of the stock or ETF
* For the assset account you are going to contribute from, use `contribution`
* For the assset account you are going to deposit from, use `deposit`
* Use `fee` on an expense account to track the wealthsimple fees
* Use `non resident withholding tax` on an expense account for the tax
* Use `nonResidentWithholdingTax` on an expense account for the tax
* In case some transaction does not balance, we will look for an expense account with `rounding`
* In case you get a refund, add `refund` to an income account
* If you want to track contribution room, use `contribution-room` on an asset and expense account (optional)
Expand All @@ -43,16 +44,14 @@ Both keys and types can be space separated in case you have multiple Wealthsimpl

```
2020-07-31 open Assets:Checking:Wealthsimple CAD
wealthsimple-account-type: "ca_cash"
wealthsimple-key: "CAD"
importer-type: "wealthsimple"
number: "A001"
2020-07-31 open Assets:Investment:Wealthsimple:TFSA:Parking CAD
wealthsimple-account-type: "ca_tfsa"
wealthsimple-key: "CAD"
importer-type: "wealthsimple"
number: "B002"
2020-07-31 open Assets:Investment:Wealthsimple:TFSA:ACWV ACWV
wealthsimple-account-type: "ca_tfsa"
wealthsimple-key: "ACWV"
2020-07-31 open Assets:Investment:Wealthsimple:TFSA:XGRO XGRO
2020-07-31 open Income:Capital:Dividend:ACWV USD
wealthsimple-account-type: "ca_tfsa"
Expand All @@ -62,7 +61,7 @@ Both keys and types can be space separated in case you have multiple Wealthsimpl
wealthsimple-account-type: "ca_tfsa"
wealthsimple-key: "contribution"
2020-07-31 open Assets:Investment:OtherComany:TFSA
2020-07-31 open Assets:Investment:OtherCompany:TFSA
wealthsimple-account-type: "ca_tfsa"
wealthsimple-key: "deposit"
Expand Down
21 changes: 21 additions & 0 deletions Sources/SwiftBeanCountWealthsimpleMapper/LedgerLookup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ struct LedgerLookup {
return symbol
}

/// Returns account name to use for a certain type of posting - not including the Wealthsimple accounts themselves
func ledgerAccountName(for account: Wealthsimple.Account, ofType type: [SwiftBeanCountModel.AccountType], symbol assetSymbol: String? = nil) throws -> AccountName {
let symbol = assetSymbol ?? account.currency
let accountType = account.accountType.rawValue
Expand All @@ -100,6 +101,26 @@ struct LedgerLookup {
return accountName
}

/// Returns account name of matching the Wealthsimple account in the ledger
func ledgerAccountName(of account: Wealthsimple.Account, symbol assetSymbol: String? = nil) throws -> AccountName {
let baseAccount = ledger.accounts.first {
$0.metaData[MetaDataKeys.importerType] == MetaData.importerType &&
$0.metaData[MetaDataKeys.number] == account.number
}
guard let accountName = baseAccount?.name else {
throw WealthsimpleConversionError.missingWealthsimpleAccount(account.number)
}
if let symbol = assetSymbol {
let name = "\(accountName.fullName.split(separator: ":").dropLast(1).joined(separator: ":")):\(try ledgerSymbol(for: symbol))"
guard let result = try? AccountName(name) else {
throw WealthsimpleConversionError.missingWealthsimpleAccount(account.number)
}
return result
} else {
return accountName
}
}

func ledgerAccountCommoditySymbol(of account: AccountName) -> String? {
let account = ledger.accounts.first {
$0.name == account
Expand Down
15 changes: 15 additions & 0 deletions Sources/SwiftBeanCountWealthsimpleMapper/MetaDataKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
// Created by Steffen Kötte on 2020-07-31.
//

/// Data in meta data used in the ledger
enum MetaData {
///
static let importerType = "wealthsimple"
}

/// Keys for meta data used in the ledger
enum MetaDataKeys {

/// Key used to save and lookup the wealthsimple transaction id of transactions in the meta data
static let id = "wealthsimple-id"

Expand All @@ -20,4 +28,11 @@ enum MetaDataKeys {

/// Key used to save the symbol of shares for which non resident witholding tax was paid
static let symbol = "symbol"

/// Key used to identify wealthsimple accounts in the ledger
static let importerType = "importer-type"

/// Key used for wealthsimple account numbers in the ledger
static let number = "number"

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum WealthsimpleConversionError: Error {
case missingCommodity(String)
/// an account was not found in the ledger
case missingAccount(String, String, String)
/// a wealthsimple account was not found in the ledger
case missingWealthsimpleAccount(String)
/// mapping of this transaction type has not been implemented yet
case unsupportedTransactionType(String)
/// the descriptions of the wealthsimple transactions is not the correct format
Expand All @@ -32,6 +34,11 @@ extension WealthsimpleConversionError: LocalizedError {
The \(category) account for account type \(accountType) and key \(key) was not found in your ledger. \
Please make sure you add the metadata \"\(LedgerLookup.keyMetaDataKey): \"\(key)\" \(LedgerLookup.accountTypeMetaDataKey): \"\(accountType)\"\" to it.
"""
case let .missingWealthsimpleAccount(number):
return """
The account for the wealthsimple account with the number \(number) was not found in your ledger. \
Please make sure you add the metadata \"\(MetaDataKeys.importerType): \"\(MetaData.importerType)\" \(MetaDataKeys.number): \"\(number)\"\" to it.
"""
case let .unsupportedTransactionType(type):
return "Transactions of Type \(type) are currently not yet supported"
case let .unexpectedDescription(string):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ public struct WealthsimpleLedgerMapper {
}
}
let balance = Balance(date: $0.positionDate,
accountName: try lookup.ledgerAccountName(for: account, ofType: [.asset], symbol: $0.asset.symbol),
accountName: try lookup.ledgerAccountName(of: account, symbol: $0.asset.symbol),
amount: balanceAmount)
if !lookup.doesBalanceExistInLedger(balance) {
balances.append(balance)
}
}
if positions.isEmpty {
let balance = Balance(date: Date(),
accountName: try lookup.ledgerAccountName(for: account, ofType: [.asset]),
accountName: try lookup.ledgerAccountName(of: account),
amount: Amount(number: 0, commoditySymbol: account.currency, decimalDigits: 0))
if !lookup.doesBalanceExistInLedger(balance) {
balances.append(balance)
Expand Down Expand Up @@ -200,7 +200,7 @@ public struct WealthsimpleLedgerMapper {

// swiftlint:disable:next cyclomatic_complexity
private func mapTransaction(_ transaction: Wealthsimple.Transaction, in account: Wealthsimple.Account) throws -> (Price?, SwiftBeanCountModel.Transaction) {
let assetAccountName = try lookup.ledgerAccountName(for: account, ofType: [.asset])
let assetAccountName = try lookup.ledgerAccountName(of: account)
var price: Price?, result: STransaction
switch transaction.transactionType {
case .buy:
Expand Down Expand Up @@ -236,7 +236,7 @@ public struct WealthsimpleLedgerMapper {

private func mapBuy(transaction: WTransaction, in account: WAccount, assetAccountName: AccountName) throws -> (Price, STransaction) {
let posting1 = Posting(accountName: assetAccountName, amount: transaction.netCash, price: transaction.useFx ? transaction.fxAmount : nil)
let posting2 = Posting(accountName: try lookup.ledgerAccountName(for: account, ofType: [.asset], symbol: transaction.symbol),
let posting2 = Posting(accountName: try lookup.ledgerAccountName(of: account, symbol: transaction.symbol),
amount: try transaction.quantityAmount(lookup: lookup),
cost: try Cost(amount: transaction.marketPrice, date: nil, label: nil))
let result = STransaction(metaData: TransactionMetaData(date: transaction.processDate, metaData: [MetaDataKeys.id: transaction.id]), postings: [posting1, posting2])
Expand Down Expand Up @@ -307,7 +307,7 @@ public struct WealthsimpleLedgerMapper {
private func mapSell(transaction: WTransaction, in account: WAccount, assetAccountName: AccountName) throws -> (Price, STransaction) {
let cost = try Cost(amount: nil, date: nil, label: nil)
let posting1 = Posting(accountName: assetAccountName, amount: transaction.netCash, price: transaction.useFx ? transaction.fxAmount : nil)
let accountName2 = try lookup.ledgerAccountName(for: account, ofType: [.asset], symbol: transaction.symbol)
let accountName2 = try lookup.ledgerAccountName(of: account, symbol: transaction.symbol)
let posting2 = Posting(accountName: accountName2, amount: try transaction.quantityAmount(lookup: lookup), price: transaction.marketPrice, cost: cost)
let result = STransaction(metaData: TransactionMetaData(date: transaction.processDate, metaData: [MetaDataKeys.id: transaction.id]), postings: [posting1, posting2])
return (try Price(date: transaction.processDate, commoditySymbol: transaction.symbol, amount: transaction.marketPrice), result)
Expand Down

0 comments on commit 66a2297

Please sign in to comment.