Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/arch/package-boundaries.org
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ compatibility facade for removed import paths.
| internal/marketdata/internal/provider/* | marketdata/internal/provider/* | No | Provider adapters owned only by the internal data subsystem |
| internal/order | order (split) | No | Canonical intents/proposals/requests/orders/fills/positions, lifecycle; depends on public order vocabulary and internal IDs |
| internal/account | account | No | Runtime accounting snapshots |
| internal/account/margin | account/margin | No | Pure gross-notional and initial-margin arithmetic shared by risk and the simulator (ADR-066) |
| internal/portfolio | portfolio | No | Cross-account projections |
| internal/backtest | backtest | No | Deterministic runner and scheduler |
| internal/broker | broker | No | Broker ports |
Expand Down
22 changes: 22 additions & 0 deletions internal/account/listingkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package account

import "github.com/rustyeddy/trader/instrument"

// ListingKey identifies one listing within an account: an economic
// instrument together with the provider and venue that list it.
//
// It is the identity at which an account holds one net position
// (runtimeorder.Position is one position per account/listing pair), and
// at which a listing is valued. Two listings of the same instrument
// from different providers or venues are different keys: they can
// carry different marks, multipliers, and settlement terms.
type ListingKey struct {
InstrumentID instrument.ID
Provider string
Venue string
}

// KeyOf returns l's ListingKey.
func KeyOf(l instrument.Listing) ListingKey {
return ListingKey{InstrumentID: l.InstrumentID(), Provider: l.Provider(), Venue: l.Venue()}
}
57 changes: 57 additions & 0 deletions internal/account/margin/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Package margin is the one shared, pure calculation of an account's
// gross notional exposure and required initial margin (ADR-066, issue
// #411). Both the account-level initial-margin risk rule (#413) and the
// simulated broker's fill-time check (#415) use it, so Trader has
// exactly one definition of gross exposure and required margin.
//
// # What it computes
//
// Gross notional is the sum of the absolute notional of every open
// position — longs and shorts both add, and nothing is netted across
// instruments:
//
// gross = Σ |quantityᵢ| × priceᵢ × multiplierᵢ
//
// Required margin is a sum of per-position required-margin amounts,
// each computed by a Policy:
//
// required = Σ policy.RequiredMargin(positionᵢ)
//
// v1 has one Policy, Ratio, which requires |notional| × ratio for every
// position, so v1's required margin is exactly gross × ratio. The
// per-position Policy is the extension point ADR-066 reserves for
// per-listing or per-instrument margin (a percentage such as OANDA's
// marginRate, or a fixed amount per futures contract). No such policy
// exists yet.
//
// # Valuation basis
//
// Every position is valued at exactly one price — never a blend of a
// current price and a position's historical AvgPrice (#183). Account
// values each open position at its current mark, supplied by the
// caller in Marks. Assess values the changed listing at the Change's
// own price for both the current and the prospective state, so
// comparing the two is a pure comparison of that listing's quantity;
// every other position is valued at its mark in both.
//
// Positions and marks are identified by account.ListingKey (instrument,
// provider, venue) — the level at which an account holds one net
// position — not by instrument alone. Two listings of the same
// instrument are separate positions with separate marks.
//
// Assessment.Increases, the de-risking classification, compares gross
// notional rather than required margin, because required margin is
// rounded and could hide a small real increase.
//
// Deciding whether a proposal is de-risking, and whether a price is
// needed at all, is the caller's job (ADR-066): a de-risking proposal
// can be admitted from quantities alone, without calling this package.
//
// # Constraints
//
// All arithmetic uses Trader's exact num types (ADR-004); there is no
// floating point. v1 supports one account currency: every listing must
// settle in the currency the caller passes, and any mismatch is
// ErrCurrencyMismatch rather than a conversion. The package performs
// no I/O, holds no state, and is safe for concurrent use.
package margin
23 changes: 23 additions & 0 deletions internal/account/margin/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package margin

import "errors"

var (
// ErrInvalidPolicy reports a margin Policy that cannot compute a
// requirement: a nil Policy, or a Ratio that is not positive.
ErrInvalidPolicy = errors.New("margin: invalid policy")

// ErrMissingMark reports an open position whose instrument has no
// valuation price in the supplied Marks. A missing mark is never
// treated as zero, and never replaced by the position's AvgPrice.
ErrMissingMark = errors.New("margin: missing mark")

// ErrCurrencyMismatch reports a listing whose settlement currency
// differs from the account currency. v1 performs no FX conversion.
ErrCurrencyMismatch = errors.New("margin: currency mismatch")

// ErrInvalidInput reports structurally unusable input, such as an
// unconstructed listing or more than one open position in the
// instrument a Change describes.
ErrInvalidInput = errors.New("margin: invalid input")
)
Loading
Loading