ops

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package ops is the L2 operations layer: the Korbit Open API v2 with THIS CLI's guarantees layered on top of the L1 primitive client (internal/korbit).

Where it sits

frontends (cli, monitor/botapi, mcp)             — no call policy of their own
          │  resolve an Operation from the catalog (Find/Catalog) and call op.Run
          ▼
internal/ops  (THIS package)                     — owns ALL retry/idempotency POLICY
          │  issues typed rawapi calls under a korbit.Policy it decides
          ▼
internal/rawapi + internal/korbit  (L1/L0)       — typed endpoints, contract-faithful executor

The command surface is the catalog: each endpoint is one registered Operation (Catalog/Find), carrying its presentation+validation metadata (OpMeta) and its behavior (Run). There is no string-keyed routing and no default-to-passthrough — an endpoint with no operation simply is not in the catalog.

The architecture rule is: POLICY LIVES HERE. The L1 client runs exactly the retry Policy it is handed (its zero Policy is a single shot); frontends just ask an Operation to Run and render the Result. ops decides — per operation, from its OpMeta.Safety — whether the underlying send may be retried and how.

Dependency rule

ops imports internal/rawapi + internal/korbit (the typed endpoints, the client + the exported retry taxonomy) and internal/cmdmeta (the shared metadata vocabulary). It does NOT import internal/spec. ops MUST NOT import internal/stream or internal/botapi: the allowed direction is stream → ops (stream consumes WalkHistory from here), never the reverse, and the frontends depend on ops, not vice versa. ops does NO output formatting, runs NO JavaScript, knows nothing about the WebSocket layer, and owns no journal policy (the journal severity decision belongs to the Recorder behind the L1 client; the only journal seam ops carries is the per-order intent/finish hook for the place protocol, which a frontend wires).

Backend contract assumptions (the money-safety foundation)

The place protocol's safety rests on three properties of the Korbit backend. Each line states the property and the code path that depends on it; if a property stops holding, that path is unsafe.

  • clientOrderId uniqueness is server-enforced: a reused id is rejected with DUPLICATE_CLIENT_ORDER_ID, never creating a second order. Every resend reuses the same id, so a lost-response resend that actually landed returns DUPLICATE instead of double-placing. This is the no-double-order guarantee.
  • HTTP 429 is pre-execution: the rate limiter rejects at the gate, before the order reaches matching. A budget-exhausted 429 is therefore a clean failure with no verification lookup (korbit.ClassRateLimited ∈ IsPreExecution). EXCEED_TIME_WINDOW is pre-execution for the same reason (a clock-gate rejection).
  • The order read path is eventually consistent: a just-accepted order can be briefly invisible to a clientOrderId lookup. A negative lookup is therefore never proof an order did not land: the lookup retries across ~1s (lookupAttempts/lookupRetryMs), and the endgame only asserts an outcome it has positive proof of (a 2xx accept or a DUPLICATE answer). An ambiguous send whose order is not visible is UNKNOWN, not failed.

The place-order protocol — why it is safe

The place operation is the flagship guarantee. Order placement is a money mover with no natural idempotency (OpMeta.Safety is nonIdempotent), so it is NEVER auto-resent by the L1 retry ladder. ops makes resending safe explicitly, using the server's own deduplication:

  • Every placement carries a clientOrderId (auto-minted UUIDv7 when the caller omits one). The server answers a REUSED id with DUPLICATE_CLIENT_ORDER_ID — so a resend of the SAME id can never place a second order.
  • The order's mint-time intent is journaled BEFORE anything is sent (a hard pre-send guarantee: a journal failure fails the placement with nothing on the wire).
  • The send is single-shot. ops then reconciles by the failure's class (internal/korbit's pre-execution vs ambiguous taxonomy):
  • EXCEED_TIME_WINDOW — provably rejected at the server's clock gate BEFORE any side effect: resync the clock ONCE and resend the same id.
  • HTTP 429 — provably rate-limited before execution: honor Retry-After (else back off) within the retry budget, then resend the same id.
  • network error / HTTP 5xx — AMBIGUOUS (the order MAY have landed): resend the SAME id; a DUPLICATE answer proves the earlier attempt landed. Bounded by the retry budget.
  • any other rejection (insufficient funds, bad price, …) — surfaced immediately; no resend.
  • Every success path (first-try, resync-corrected, duplicate-resolved) returns the FULL fetched order — one shape for the caller.
  • The order read path is EVENTUALLY CONSISTENT, so the endgame only ever ASSERTS an outcome it has positive proof of; a negative lookup is never read as "not placed". After a 2xx accept or a DUPLICATE answer the order IS placed: the lookup is retried across ~1s, and if it still can't be read back we return the accept acknowledgement (2xx, carrying the orderId) or re-surface DUPLICATE_CLIENT_ORDER_ID (duplicate) — never "failed". Only the AMBIGUOUS (network/5xx) budget-exhausted case is genuinely unsure: its lookup either finds the order (return it) or yields state-UNKNOWN (throw with verification instructions). 429/EXCEED_TIME_WINDOW are pre-execution, so they surface as clean failures with no lookup. We never GUESS about money, and we never call a placed order failed.

The honesty rule (never silently truncate)

The cursorless history endpoints (order.history, fills) and the funding histories can only return a bounded window, and candles past the server's 200-row cap must be paged. When a requested window cannot be fully covered, ops returns the rows it DID get with Result.Truncated set and a Result.Note explaining the limit — it never silently under-delivers. A frontend renders that out-of-band (the bot API attaches truncated/note array properties and prints the note to stderr; a CLI consumer renders it differently).

Index

Constants

View Source
const CandlesMaxLimit = 5000

CandlesMaxLimit is the auto-paging ceiling for the candles operation: 25 requests of the server's 200-candle pages. Far beyond indicator warm-up needs while keeping a typo'd limit from turning into an unbounded crawl.

View Source
const HistoryMaxRows = historyPageLimit * maxHistoryPages

HistoryMaxRows is the largest --limit (total-row cap) the history/fills operations accept: the window walk's natural ceiling of maxHistoryPages full server pages. A larger --limit could never be satisfied anyway, so it is the validation Max; the walk still requests legal server-sized pages and the operation trims the deduped result to the requested cap.

View Source
const HistoryWindowMs = 36 * 60 * 60 * 1000

HistoryWindowMs is the documented /v2/allOrders + /v2/myTrades retention (36h per the public API docs), the default lookback for an unbounded history walk. It lives here (the operations layer) because the walk policy is an ops concern; the stream layer re-exports it for its own backfill window.

View Source
const SimulationDisclaimer = "" /* 177-byte string literal not displayed */

SimulationDisclaimer is attached to every PlaceSimulation. It is load-bearing: the numbers are a simulation against the current public orderbook, NOT the outcome of a real placement — the book moves between this estimate and a real send, and hidden/iceberg liquidity and fees are not modeled.

Variables

This section is empty.

Functions

func AnalyzePlace

func AnalyzePlace(values map[string]string, bidLevels, askLevels []BookLevel, bands []TickBand) (PlaceSimulation, []PlaceWarning, error)

AnalyzePlace is the pure core of the pre-place analysis: the same simulation and warnings as PrePlaceCheck, over a caller-supplied orderbook snapshot and (optional) tick-size policy — no I/O. values are the order's validated wire params (the same map the place operation runs on); bids/asks are the book's levels in any order (they are sorted defensively); nil/empty bands skip the tick-alignment check. The error reports an unusable (empty) book — the analysis needs both sides for a mid.

func MintClientOrderID

func MintClientOrderID() string

MintClientOrderID returns a fresh clientOrderId. The caller mints it (on its own goroutine, before any dispatch) so the same id is reused across every resend the reconcile protocol makes. Exposed so a frontend that wants the id minted on a specific goroutine (e.g. botapi's JS event loop, where the param must be appended before the work is handed to the worker pool) controls the ordering; the place operation also mints one itself when the caller did not.

func OnTick

func OnTick(bands []TickBand, price string) (onGrid, ok bool)

OnTick reports whether price sits exactly on its band's tick grid. ok=false when the grid can't be resolved (empty/unusable policy, or price unparseable or below every band) — the caller then can't check and defers to the server.

func PrePlaceCheck

func PrePlaceCheck(ctx context.Context, raw *rawapi.Client, values map[string]string) (PlaceSimulation, []PlaceWarning, error)

PrePlaceCheck runs the customer-protection analysis behind `order place --dry-run`. It fetches PUBLIC market data only (orderbook + tick-size policy) through the supplied creds-less client, so it signs nothing and works before any key is set up. The returned warnings are advisory. A non-nil error means the orderbook could not be fetched (offline, or an invalid/untradable symbol) and the analysis was skipped — the caller still emits its plan, just without the safety checks.

The orderbook is sufficient for every order type: its top levels ARE the best bid/ask, and walking it simulates exactly what a marketable order (a market / best order, or the crossing portion of a limit order) would fill at.

func SnapToTick

func SnapToTick(bands []TickBand, price string) (string, bool)

SnapToTick floors price onto its band's tick grid (the largest grid point not exceeding it). A price already on the grid comes back unchanged (in canonical form). ok=false under the same conditions as TickSizeAt.

func SnapUpToTick

func SnapUpToTick(bands []TickBand, price string) (string, bool)

SnapUpToTick ceils price onto its band's tick grid (the smallest grid point at or above it). A price already on the grid comes back unchanged (in canonical form). ok=false under the same conditions as TickSizeAt.

func StepTicks

func StepTicks(bands []TickBand, price string, n int) (string, bool)

StepTicks moves price by n grid steps (n > 0 up, n < 0 down) along the tick-size grid, snapping the starting price onto the grid first and re-resolving the band as a step crosses a band edge. Stepping clamps rather than leaving the grid: a step below the lowest grid point (or to zero) stays put. ok=false under the same conditions as TickSizeAt.

func TickSizeAt

func TickSizeAt(bands []TickBand, price string) (string, bool)

TickSizeAt returns the tick size applicable at price (the band with the greatest PriceGte not exceeding it). ok=false when the policy is empty or unusable, or the price is unparseable or below every band.

func WalkHistory

func WalkHistory(get func(params []korbit.KV) (json.RawMessage, error), base []korbit.KV, startMs, endMs int64, tsField string, emitPage func(rows []json.RawMessage)) (complete bool, err error)

WalkHistory pages a cursorless history endpoint (/v2/allOrders, /v2/myTrades) until the requested window (startTime >= startMs; startTime inclusive, endTime exclusive) is fully covered. The server returns rows newest-first, so a saturated page truncated the OLDEST rows and the next page sets endTime to the page's oldest timestamp + 1 (the +1 keeps the boundary rows in range — endTime is exclusive — so pages overlap rather than risk a skip; overlap duplicates are removed by the caller's dedupe or are idempotent re-states). The sort order is observed, not contractual, so each page's direction is re-detected from its row timestamps: an oldest-first page paginates forward by startTime instead. Every page is handed to emitPage as it arrives. Returns complete=false when the window could not be fully covered (page cap hit, rows missing the timestamp field, no pagination progress) so the caller can TELL the consumer instead of silently under-delivering.

get runs one page request: it receives the paging params (limit, startTime, optional endTime) appended to base and must return the endpoint's data document (a JSON array). endMs, when > 0, bounds the window's upper edge (endTime exclusive); 0 means "now". This is the engine the stream layer's reconnect backfill and the bot API's order.history/fills bindings both run.

Types

type API

type API struct {
	// Raw is the typed endpoint layer the registered Operations call, over the
	// journaled wire client. NewAPI sets it (rawapi.New over the client). Required.
	Raw *rawapi.Client
	// Resync re-measures the server clock once when a single-shot send is
	// rejected with EXCEED_TIME_WINDOW (provably pre-execution) — the place
	// protocol's clock-resync hook (the ONLY user of this field, since that
	// protocol drives single-shot sends through its own reconcile loop rather than
	// the L1 client's retry, which corrects EXCEED_TIME_WINDOW itself). NewAPI
	// seeds it from korbit.Client.Resync — the SAME resync primitive — so the two
	// never diverge. nil disables that one corrective resync.
	Resync func() error
	// ServerNow is the server-clock estimate in unix ms, used to default the
	// history walk's lookback window (a server-relative time filter sent on the
	// wire). NewAPI seeds it from korbit.Client.ServerNowMs — the same clock the
	// client signs against. nil falls back to the local wall clock. (The journal's
	// row times do NOT use this — they are stamped by the frontend's own system
	// clock in internal/callrec.)
	ServerNow func() int64
	// Sleep is the inter-retry delay primitive for the place protocol's budgeted
	// waits; NewAPI seeds it from korbit.Client.Sleep. nil = time.Sleep.
	Sleep func(time.Duration)
	// RetryBudgetMs bounds the place protocol's total reconcile sleep and the
	// default per-call retry budget handed to idempotent passthrough calls.
	RetryBudgetMs int
	// Stderr receives the truncation notes the honesty rule emits. The frontend
	// decides whether to mirror them as structured output; ops only writes the
	// human note here. nil = io.Discard.
	Stderr io.Writer
	// Journal is the operations-ledger seam: every Operation.Run begins an
	// operation here (which makes the persist decision once, pre-send) and threads
	// the returned handle through ctx so the recording doer behind Raw groups the
	// operation's api_calls under it, and the place protocol records its order
	// intent through it. nil disables journaling (a no-op handle is used).
	Journal OpJournal
	// Log is the optional operational logger for the operation-LEVEL decisions the
	// wire layer below cannot see. NewAPI seeds it from the client's own logger
	// (one wiring per surface); set it afterwards only to give ops a distinct
	// logger. It covers: the place reconcile protocol's send/resend/
	// duplicate/lookup trail and final verdict, and the history/candles paging
	// summary. nil = silent (resolved once via logging.Or). Decisions and outcomes
	// (including the final verdict) log at Debug and per-iteration detail at Trace;
	// nothing logs at Warn here, because every money-unsafe verdict
	// (UNKNOWN/placed-but-unreadable/duplicate-placed) ALREADY reaches the user as
	// program output (the returned error + the guidance note), so re-emitting it on
	// the operational log would only duplicate that on the shared stderr sink — the
	// Debug line is the structured copy for the combined --log-file trail. It NEVER
	// logs a secret: only non-sensitive request facts (clientOrderId, symbol, side)
	// and outcomes (price/qty ride the journal, not the log). A single-call
	// passthrough is left to the wire layer's own log, so a routine read adds no
	// ops line.
	Log *slog.Logger
}

API is the L2 operations layer over the typed L1 endpoints. It owns ALL retry and idempotency policy: every operation decides the korbit.Policy the wire client will execute and the reconcile protocol around it. A frontend builds one API and asks an Operation to Run; it never decides call policy itself.

Build one with NewAPI, which seeds the clock/time/scheduling seams (Resync, ServerNow, Sleep, Log) from the *korbit.Client the Operations call through, so they cannot drift from the client that actually signs and sends. A frontend then sets only the ops-level policy (RetryBudgetMs, Journal, Stderr). The zero RetryBudgetMs is treated as "no budgeted (sleeping) retries", same as a single shot for the budgeted classes.

func NewAPI

func NewAPI(client *korbit.Client) *API

NewAPI builds the L2 operations layer over a wire client. It is the single production construction site: the typed endpoint layer (Raw) and the clock/time/scheduling seams (Resync, ServerNow, Sleep) and the operational logger (Log) are all derived from the one *korbit.Client the Operations call through, so they cannot drift from the client that actually signs and sends — the same resync primitive, the same clock estimate, the same sleep and logger. The caller then sets only the ops-level policy that the client does not own: RetryBudgetMs (the reconcile/idempotent-retry budget), Journal (the operations-ledger seam), and Stderr (the honesty-note sink).

Tests build *API directly to inject scripted seams over a rawapi.Doer fake; NewAPI is the production path that ties the seams to a real client.

type BookLevel

type BookLevel struct {
	Price string
	Qty   string
}

BookLevel is one orderbook level as its wire strings (price and base quantity, both decimal strings). It is the transport-free form AnalyzePlace consumes, so a caller can feed levels from any source — the REST fetch PrePlaceCheck does, or a live WebSocket book a UI already holds.

type Controls

type Controls struct {
	SkipReconcile bool   // honored only by the place operation; ignored elsewhere
	Surface       string // the frontend identity for the operations ledger: cli|monitor|mcp
}

Controls carries per-invocation behavior toggles a frontend selects.

type OpHandle

type OpHandle interface {
	// OperationID is the operations row id (0 when not recording).
	OperationID() int64
	// ForCall returns a per-api_call recorder bound to this operation, assigning
	// the next 1-based sequence within it. orderedParams overrides the recorded
	// params_json with the spec/insertion order ("" keeps the wire client's). It
	// returns nil when the operation is not recorded; the wire client treats a nil
	// Recorder as "do not record", so the send still proceeds.
	ForCall(orderedParams string) korbit.Recorder
	// StartOrder records the order's mint-time intent BEFORE the placement send
	// (the hard pre-send guarantee) and returns the finish hook. An error aborts
	// the placement with nothing on the wire.
	StartOrder(OrderIntent) (OrderFinishFunc, error)
	// Finish folds the operation result into its ledger row. The api_calls count
	// is the handle's own (it counted every ForCall) and the finish time comes from
	// the frontend's own clock, so callers pass only the outcome. A finish-write
	// failure is diagnostic, not a money-safety signal; it is returned for the
	// caller to surface or warn as its surface dictates.
	Finish(outcome, errorCode string) error
}

OpHandle is the per-operation journal handle a frontend's OpJournal returns from Begin. It groups the operation's api_calls under one operations row and holds the (already-made) persist decision: a non-recording handle no-ops everything and opens nothing.

func HandleFromContext

func HandleFromContext(ctx context.Context) OpHandle

HandleFromContext returns the operation's OpHandle from ctx, or nil if absent. The recording doer reads it to obtain a per-call recorder (ForCall) carrying the operation id + sequence; an absent handle leaves the call unrecorded.

type OpJournal

type OpJournal interface {
	Begin(OpStart) (OpHandle, error)
}

OpJournal is the frontend-supplied journal seam: Begin makes the persist decision once and opens the operations ledger row (pre-send) when recording.

type OpMeta

type OpMeta struct {
	ID          []string // command path segments, e.g. {"order","place"}
	Method      string   // REST method the operation maps to: GET | POST | DELETE
	Path        string   // REST path the operation maps to, e.g. /v2/orders
	Section     cmdmeta.Section
	Summary     string
	Params      []cmdmeta.Param
	Positionals []cmdmeta.Positional
	Notes       []string
	Examples    []string
	// ExperimentalNotes / ExperimentalExamples document an opt-in, not-yet-stable
	// feature; plain `--help` hides them and --enable-experimental reveals them
	// (the machine catalog always includes them). No endpoint operation is
	// experimental today — the fields exist so the unified surface stays symmetric
	// with the builtin spec.
	ExperimentalNotes    []string
	ExperimentalExamples []string
	Response             []cmdmeta.ResponseField
	Auth                 *cmdmeta.Auth // nil = public
	Safety               cmdmeta.Safety
	Destructive          bool // MCP destructive hint (money-movers + cancels)
	// CrossValidate applies cross-field rules after per-value normalization; nil
	// = none. values is keyed by API parameter name (post-normalization).
	CrossValidate func(values map[string]string) error
}

OpMeta is an operation's presentation + validation metadata; the catalog is the set of OpMetas.

func (OpMeta) Key

func (m OpMeta) Key() string

Key returns the space-joined command key, e.g. "order place".

type OpStart

type OpStart struct {
	OpID     string // dotted operation id, e.g. "order.place"
	Surface  string // the frontend that issued it: cli|monitor|mcp|...
	Safety   cmdmeta.Safety
	Auth     bool
	KeyName  string
	APIKeyID string
}

OpStart describes a logical operation as it begins, for the operations ledger. The frontend's OpJournal stamps the row times from its own (system) clock, so ops supplies no timestamp.

type Operation

type Operation interface {
	Meta() OpMeta
	Run(ctx context.Context, a *API, in RunInput) (Result, error)
}

Operation is one catalog entry: its metadata and its behavior.

func Catalog

func Catalog() []Operation

Catalog returns a copy of the registered operations in registration order.

func Find

func Find(id ...string) Operation

Find resolves an operation from command-path segments, preferring the deepest matching operation (mirrors spec.Find) so a nested leaf wins over a shorter prefix. nil when no operation matches.

type OrderFinishFunc

type OrderFinishFunc func(status, orderID, errorCode string, attempts int)

OrderFinishFunc folds the placement outcome into the order's journal row. Best-effort: a failure here is reported out-of-band, never turned into a placement failure the caller could mistake for "not placed".

type OrderIntent

type OrderIntent struct {
	ClientOrderID string
	Symbol        string
	Side          string
	OrderType     string
	Price         string
	Qty           string
	Amt           string
	Tif           string
	ParamsJSON    string
}

OrderIntent is an order's mint-time intent, recorded BEFORE the placement send (the hard pre-send guarantee). It is keyed to the operation by the OpHandle. The frontend stamps the row's create time from its own (system) clock.

type PlaceSimulation

type PlaceSimulation struct {
	// Disclaimer is always set and states plainly that this is an estimate.
	Disclaimer string `json:"disclaimer"`

	BestBid     string `json:"bestBid"`
	BestAsk     string `json:"bestAsk"`
	Mid         string `json:"mid"`
	NotionalKRW string `json:"notionalKrw,omitempty"`

	// EstPegPrice is the price a best (BBO) order derives from the current book —
	// its --best-nth level on the side its tif selects. Empty for non-best orders
	// and when that level is not visible in the book.
	EstPegPrice string `json:"estPegPrice,omitempty"`

	// Marketable is true when the order (or a limit's crossing portion) would
	// take liquidity immediately. The Est* fields describe that simulated taker
	// fill; they are empty for a non-crossing limit (which rests in full).
	Marketable        bool   `json:"marketable"`
	EstFilledQty      string `json:"estFilledQty,omitempty"`
	EstFilledQuote    string `json:"estFilledQuoteKrw,omitempty"`
	EstAvgFillPrice   string `json:"estAvgFillPrice,omitempty"`
	EstWorstFillPrice string `json:"estWorstFillPrice,omitempty"`
	EstSlippagePct    string `json:"estSlippagePct,omitempty"`

	// FullyFilled reports whether the whole order size is covered by the visible
	// marketable book.
	FullyFilled bool `json:"fullyFilled"`
	// EstRemainingQty is the base quantity that would NOT fill immediately;
	// Disposition says what becomes of it (rests as a maker limit, or is canceled
	// for a market/IOC order). It is omitted only for a market BUY (quote-sized, so
	// a base remainder is undefined — Disposition explains); a best BUY is
	// base-sized (amt is converted to qty at the peg) and does report it.
	EstRemainingQty string `json:"estRemainingQty,omitempty"`
	Disposition     string `json:"remainingDisposition,omitempty"`
}

PlaceSimulation is the estimated outcome of the order against the current public orderbook — what the order WOULD do if sent now. Every money/quantity field is a decimal string. It is a simulation, not a guarantee (see SimulationDisclaimer).

type PlaceWarning

type PlaceWarning struct {
	// Code is a stable symbolic identifier an agent can branch on.
	Code PlaceWarningCode `json:"code"`
	// Message is the human-readable warning in English, rendered from Format and
	// Args. It is the value shipped in the `--json` contract.
	Message string `json:"message"`
	// Format and Args are the un-rendered message: Format is the English template
	// (printf %s verbs only) and Args its interpolation values, with Message equal
	// to fmt.Sprintf(Format, Args...). They let a consumer re-render the message
	// its own way — fmt.Sprintf for English, or a localizing printer keyed on the
	// Format string for another language — so this layer needs no localization
	// dependency of its own. Not part of the JSON contract (localization is a
	// display concern of the consumer, not the wire shape agents read).
	Format string `json:"-"`
	Args   []any  `json:"-"`
}

PlaceWarning is one advisory customer-protection finding produced by the pre-place dry-run analysis. It never blocks a placement; it tells the caller the order is risky (would sweep the book to a bad price, would be rejected by the server, looks like a fat-finger) so an agent can decide before sending.

type PlaceWarningCode

type PlaceWarningCode string

PlaceWarningCode is the stable symbolic identifier of a pre-place warning — the value of PlaceWarning.Code. Its string values are part of the `--json` contract an agent branches on, so they never change; the typed constants below are the single source for the set (referenced by the analysis that emits them and by any consumer that switches on them).

const (
	WarnPriceFarAboveMarket   PlaceWarningCode = "PRICE_FAR_ABOVE_MARKET"
	WarnPriceFarBelowMarket   PlaceWarningCode = "PRICE_FAR_BELOW_MARKET"
	WarnPostOnlyWouldReject   PlaceWarningCode = "POST_ONLY_WOULD_REJECT"
	WarnFOKWouldKill          PlaceWarningCode = "FOK_WOULD_KILL"
	WarnIOCWouldExpire        PlaceWarningCode = "IOC_WOULD_EXPIRE"
	WarnNotionalBelowMin      PlaceWarningCode = "NOTIONAL_BELOW_MIN"
	WarnNotionalAboveMax      PlaceWarningCode = "NOTIONAL_ABOVE_MAX"
	WarnPriceOffTick          PlaceWarningCode = "PRICE_OFF_TICK"
	WarnInsufficientLiquidity PlaceWarningCode = "INSUFFICIENT_LIQUIDITY"
	WarnHighSlippage          PlaceWarningCode = "HIGH_SLIPPAGE"
	WarnBookDepthLimited      PlaceWarningCode = "BOOK_DEPTH_LIMITED"
	WarnPriceProtectionCapped PlaceWarningCode = "PRICE_PROTECTION_CAPPED"
	WarnBestPegUnavailable    PlaceWarningCode = "BEST_PEG_UNAVAILABLE"
)

type Result

type Result struct {
	Data      json.RawMessage
	Truncated bool
	Note      string
	Attempts  int
	// JournalErr is an OUT-OF-BAND operations-ledger write failure (the Finish
	// folding the operation outcome into its ledger row failed). It is never the
	// operation's own success/failure — a frontend surfaces it the way it surfaces
	// the other journal errors (the cli joins it and fails after the result; the
	// warn-policy surfaces have already warned and leave it nil).
	JournalErr error
}

Result is the outcome of an Invoke or a list operation. Data is the response document (for the place protocol, the full fetched order). Truncated/Note are the honesty-rule signals carried OUT-OF-BAND so a frontend can render them in its own idiom (array properties + stderr for the bot, a different shape for a CLI). Attempts echoes how many sends the operation took, for callers that surface it.

type RunInput

type RunInput struct {
	Values   map[string]string // validated values keyed by API name
	Controls Controls
	// KeyName / APIKeyID identify the signing key for the operations ledger (the
	// public api-key id; never a secret). "" for a public operation. They feed
	// only the journal — signing credentials live on the wire client behind Raw.
	KeyName  string
	APIKeyID string
}

RunInput is the validated, normalized input a frontend hands an operation.

type TickBand

type TickBand struct {
	PriceGte string
	TickSize string
}

TickBand is one price band of a symbol's tick-size policy: TickSize applies at prices at or above PriceGte. Both are decimal strings, as served by GET /v2/tickSizePolicy.

Jump to

Keyboard shortcuts

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