model

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package model is the common language between sources and destinations: every platform's history is normalized into Activities, every platform's holdings into Balances. Amounts are exact decimals, never floats.

It also holds what is computed across sources: transfers between your own accounts (PairTransfers), cost basis by replaying the history with average cost per source, transfers carrying their cost (Books, Costs), and the balance check (Reconcile, Adjustments). Activities from Fundarr's API decode into Activity; see pkg/api.

Index

Constants

View Source
const (
	AdjustPrefix = "adjust:" // written by Fundarr's balance check
	ManualPrefix = "manual:" // entered by the user (a reclassified adjustment)
)

NativeID prefixes of activities that do not come from the platform.

Variables

This section is empty.

Functions

func ActivityID

func ActivityID(sourceID, nativeID string) string

ActivityID derives the stable id of an activity from where it came from.

func CanPair

func CanPair(out, in Activity) bool

CanPair reports whether the user may link out and in by hand.

func CashLike

func CashLike(a Asset) bool

CashLike is true for fiat money and stablecoins: what assets are priced in.

func Costs

func Costs(acts []Activity, pairs map[string]string, value Valuer) map[string]decimal.Decimal

Costs replays every source's history together and returns, for each activity that brings a non-cash asset in, what that asset cost, in the Valuer's currency:

  • bought with cash: the cash paid;
  • swapped for another asset, or moved from another of your accounts (a paired transfer): the cost carried by what left, at its average cost;
  • received for nothing (rewards, airdrops, deposits from outside, adjustments): its market value that day.

Activities whose cost is unknown (no price) are left out. pairs maps a deposit's id to the withdrawal it came from.

func Holdings

func Holdings(acts []Activity) map[string]Amount

Holdings replays activities and returns the quantity of each asset they add up to. Comparing it with what the platform reports (Reconcile) is how we know a source understood the whole history.

func PeggedTo

func PeggedTo(a Asset) string

PeggedTo returns the fiat currency a stablecoin follows ("" otherwise).

func Transferable

func Transferable(a Activity) bool

Transferable reports whether an activity can be one side of a transfer between your own accounts. Fiat moves through banks Fundarr does not see.

Types

type Activity

type Activity struct {
	ID       string    `json:"id"`       // stable: hash of source and native id
	SourceID string    `json:"sourceId"` // the configured source it came from
	NativeID string    `json:"nativeId"` // the platform's own id (or a stable composite)
	Account  string    `json:"account"`  // sub-account within the platform, "" for the main one
	Time     time.Time `json:"time"`
	Type     Type      `json:"type"`
	Side     Side      `json:"side,omitempty"` // trades (buy, sell) and adjustments (in, out)

	Base  Amount  `json:"base"`            // the asset bought/sold/moved/received
	Quote *Amount `json:"quote,omitempty"` // trades: what was paid or received in exchange
	Fee   *Amount `json:"fee,omitempty"`   // fee charged with it, in any asset

	Network string `json:"network,omitempty"` // deposits/withdrawals: chain (BTC, ETH, TRX...)
	TxID    string `json:"txId,omitempty"`    // on-chain hash or bank reference
	Address string `json:"address,omitempty"`
	Note    string `json:"note,omitempty"` // human description from the platform
}

Activity is one movement on one platform, as faithful to the source as possible; destinations translate it into their own vocabulary.

func Adjustments

func Adjustments(acts []Activity, gaps []Gap, now time.Time) []Activity

Adjustments returns the corrections that make acts add up to the reported balances (gaps from Reconcile on the same acts).

Where history shows an asset leaving before it ever arrived (a purchase the platform no longer reports), the missing quantity is added just before the balance would go negative, so it is valued at that day's price and replayed in order. Whatever else is missing or extra is corrected at now. Ids depend only on the content: the same gap gives the same adjustment on every run.

func ReplayOrder

func ReplayOrder(acts []Activity, pairs map[string]string) []Activity

ReplayOrder sorts activities in time order, a deposit after the withdrawal it came from even when the receiving platform's clock says otherwise. pairs maps a deposit's id to its withdrawal.

func (Activity) Inflow

func (a Activity) Inflow() bool

Inflow reports whether the activity brings its Base into the account.

func (Activity) Outflow

func (a Activity) Outflow() bool

Outflow reports whether the activity takes its Base out of the account.

func (Activity) Price

func (a Activity) Price() decimal.Decimal

Price returns the unit price of a trade (Quote per Base).

type Amount

type Amount struct {
	Asset Asset           `json:"asset"`
	Qty   decimal.Decimal `json:"qty"`
}

Amount is a quantity of an asset. Qty is always positive; the activity type and side say which way it moves.

func Amt

func Amt(a Asset, qty string) Amount

type Asset

type Asset struct {
	Kind   AssetKind `json:"kind"`
	Symbol string    `json:"symbol"`
	ISIN   string    `json:"isin,omitempty"`
	Name   string    `json:"name,omitempty"`
}

Asset identifies what is held. Symbol is the platform's ticker, upper-cased (BTC, EUR, IWDA); ISIN is set for securities when the platform gives it.

func CryptoOrFiat

func CryptoOrFiat(symbol string) Asset

CryptoOrFiat builds the asset for a currency code as exchanges report them.

func (Asset) Key

func (a Asset) Key() string

Key identifies an asset across activities and balances.

func (Asset) String

func (a Asset) String() string

type AssetKind

type AssetKind string

AssetKind says how an asset is priced and where it can live.

const (
	Crypto   AssetKind = "crypto"   // coins and tokens, including stablecoins
	Fiat     AssetKind = "fiat"     // EUR, USD... held as cash
	Security AssetKind = "security" // stocks, ETFs, bonds (identified by ISIN when known)
)

type Balance

type Balance struct {
	Account string `json:"account"`
	Amount
}

Balance is what a platform says is held right now.

type Book

type Book struct {
	Costs map[string]decimal.Decimal
	// Taken: by activity id, the cost of what a trade gave or an outflow
	// sent (cash at its market value); Fees: of a fee paid apart.
	Taken, Fees map[string]decimal.Decimal
	// contains filtered or unexported fields
}

Book is the replay behind Costs, with what left the pools along the way and what they hold at the end.

func Books

func Books(acts []Activity, pairs map[string]string, value Valuer) *Book

Books replays like Costs and keeps the book.

func (*Book) Held

func (b *Book) Held(src string, a Asset) (qty, cost decimal.Decimal)

Held is the quantity and cost left in one source's pool of an asset (zero when history shows more leaving than arriving).

type Gap

type Gap struct {
	Asset    Asset           `json:"asset"`
	Replayed decimal.Decimal `json:"replayed"`
	Reported decimal.Decimal `json:"reported"`
	Diff     decimal.Decimal `json:"diff"` // Reported - Replayed
}

Gap is a difference between replayed history and reported balance.

func Reconcile

func Reconcile(acts []Activity, balances []Balance, tolerance decimal.Decimal) []Gap

Reconcile lists assets whose replayed quantity differs from the reported balance by more than tolerance (relative to the larger of the two, with an absolute floor for dust). Sorted by asset.

type Pair

type Pair struct {
	OutID string `json:"outId"`
	InID  string `json:"inId"`
	// By says how the two were matched: "txid" (same transaction hash),
	// "amount" (same asset and amount, a few days apart) or "user".
	By string `json:"by"`
}

Pair links a withdrawal on one source to the deposit it became on another (Binance → Ledger, OKX → Binance): the coins stayed yours, so destinations move them between accounts and carry their cost over.

func PairTransfers

func PairTransfers(acts []Activity, forced []Pair, refused map[string]bool) []Pair

PairTransfers matches withdrawals with deposits across sources. forced pairs (chosen by the user) come first; refused ones ("out\x00in") are never made. Each activity is in at most one pair. Hashes win over amounts; among amount matches the closest in time wins.

type Side

type Side string
const (
	Buy  Side = "buy"
	Sell Side = "sell"
	In   Side = "in"  // adjustments only
	Out  Side = "out" //
)

type Type

type Type string

Type is what happened.

const (
	// Trade: Side of Base against Quote (buy BTC with USDT, sell IWDA for EUR,
	// a "convert", a card purchase of crypto).
	Trade Type = "trade"
	// Deposit / Withdrawal: Base enters or leaves the platform (crypto
	// transfer, bank transfer, card top-up). TxID and Network help matching
	// the other side when it is another platform or wallet you own.
	Deposit    Type = "deposit"
	Withdrawal Type = "withdrawal"
	// Reward: Base received for nothing in return (staking, savings/earn
	// interest paid in crypto, airdrop, referral, distribution).
	Reward Type = "reward"
	// Dividend: cash Base paid by the security in Quote.Asset (Quote.Qty 0).
	Dividend Type = "dividend"
	// Interest: cash interest on a fiat balance.
	Interest Type = "interest"
	// Fee: a charge not tied to another activity (custody, account fees).
	Fee Type = "fee"
	// Tax: withholding or other tax taken by the platform.
	Tax Type = "tax"
	// Adjustment: a correction Fundarr makes when the history a platform
	// gives does not add up to what it holds. Side In adds Base, Out removes
	// it. NativeID starts with AdjustPrefix.
	Adjustment Type = "adjustment"
)

type Valuer

type Valuer func(a Amount, at time.Time) (value decimal.Decimal, ok bool)

Valuer gives the market value of an amount at a time, in one currency. ok is false when no price is known.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL