> ## Documentation Index
> Fetch the complete documentation index at: https://docs.splashsports.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fees and order cost

> Work out to the cent how much cash an order takes before you send it. The fee formula, the rounding, the maximum trade, and a calculator to check against your balance.

Splash charges a fee on every order. On a buy, the fee is added to the cost of the contracts and both are taken from your
cash when the order is submitted. Your fee settings are on your account, so you can compute the exact amount an order
will take, compare it with your balance, and only then send the order.

You never send the fee. An order is the same raw order as always (`market_id`, `side`, `quantity`, `price`,
`time_in_force` and `request_id`), and Splash computes the fee itself from your fee settings. This page gives the
formula Splash uses, so the number you compute is the number Splash takes.

## Before you send an order

1. **Read your account** with [`GET /account`](https://docs.splashsports.com/predictions/reference/account/get-account): `fee` is your fee settings
   and `max_trade` is the maximum trade. Both change rarely, so read them at start-up rather than before every order.
2. **Read your balance** with [`GET /balance`](https://docs.splashsports.com/predictions/reference/account/get-balance): `cash_available` is the cash
   you can spend now.
3. **Compute the order's cost**: the contracts cost, the fee and the debit, as below.
4. **Check both limits**: the debit must be at most `cash_available`, and the contracts cost at most `max_trade`.
5. **Send the raw order** to [`POST /trading/orders`](https://docs.splashsports.com/predictions/reference/orders/place-an-order) and follow it as in
   [Placing an order](https://docs.splashsports.com/predictions/trading.md#placing-an-order-and-following-it).

The two limits fail differently. An order whose contracts cost is more than `max_trade` is refused at once with
`400 Order exceeds the maximum trade of $<limit>`, and its `request_id` is spent. An order whose debit is more than your
cash is accepted (`200` with `order_status: "pending"`) and then ends `cancelled` without filling, so you only learn about
it by polling. Checking first saves you both.

## The fee

`fee` on [`GET /account`](https://docs.splashsports.com/predictions/reference/account/get-account) looks like this:

```json theme={null}
{ "type": "formula", "formula_type": "symmetric_price_uncertainty", "theta": 0.07 }
```

**fee = `theta` × `quantity` × `price` × (1 − `price`)**

The fee is largest at a price of $0.50 and shrinks toward $0.01 and \$0.99. The formula gives the same number for `price`
and 1 − `price`, so it is the same whichever side you trade, and on a buy or a close.

* **Rounding.** Multiply in that order, rounding each product to 6 decimal places, half up: `theta` × `quantity`, then
  × `price`, then × (1 − `price`). Round the result to the cent **half to even** (banker's rounding): $0.105 becomes
  $0.10 and $0.035 becomes $0.04. Every other amount on this page rounds half up; this is the one step that does not.
* **Example.** 10 contracts at $0.59, theta 0.07: 0.07 × 10 × 0.59 × 0.41 = $0.16933, so the fee is \$0.17.

Read `theta` from your own account rather than copying 0.07 from these examples.

## What a buy costs

A buy opens contracts, or adds to the ones you hold on the same side.

* **Contracts cost** = `quantity` × `price`, exactly. `price` is the price you send, in the terms of `side`: a `no` order
  is priced in NO terms. A sub-cent price such as `0.655` is used as sent.
* **Fee**: as above.
* **Debit** = contracts cost + fee, rounded to the cent, half up. The fee is always a whole number of cents, so this
  rounds only the contracts cost: 13 contracts at $0.655 cost $8.515, and with a fee of $0.29 the debit is $8.805,
  rounded to \$8.81.

The debit is the most a buy costs you. It is taken when the order is submitted. If the order fills at a better price,
Splash works out the fee again at the fill price and credits back whatever the debit came to above the fill plus that
fee. If the fill-or-kill order cannot fill, the whole debit is returned. If the fee at the fill price would bring the
total above the debit, you are still charged no more than the debit.

A buy at a sub-cent price is sent to the market at the cent below, so part of its debit can come back as a refund even
when it fills at your limit: a buy at $0.655 is debited at $0.655 a contract and placed at \$0.65.

**Maximum trade.** The contracts cost, not the debit, is checked against `max_trade`: the fee is not counted. With a
`max_trade` of 100000, 169500 contracts at $0.59 cost $100,005.00 and are refused, whatever the fee; 169491 contracts
cost \$99,999.69 and are allowed.

## What a close costs

A close sells the contracts you hold: an order on the other side for exactly the number you hold (see `side` on
[Place an order](https://docs.splashsports.com/predictions/reference/orders/place-an-order.md)). Its `price` is in the terms of the side you send, so
to sell 10 YES contracts at \$0.66 you send `side: "no"`, `price: 0.34`.

* **Nothing is debited**, so a close needs no cash and is never limited by `max_trade`.
* **When it fills, you are credited the proceeds less the fee**: `quantity` × (1 − `price`) − fee, rounded to the cent,
  half up. The fee is computed at the fill price, so at your limit price this is the least you receive, give or take a
  cent of fee.

Selling 10 YES at $0.66 with theta 0.07: the proceeds are 10 × 0.66 = $6.60, the fee is 0.07 × 10 × 0.34 × 0.66 =
$0.15708, rounded to $0.16, and you are credited \$6.44.

## Rounding at a glance

| Amount | How it is computed and rounded |
| - | - |
| Contracts cost | `quantity` × `price`, exact. |
| Fee | Each product to 6 places half up; the total to the cent **half to even**. |
| Debit (buy) | Contracts cost + fee, to the cent half up. |
| Credit (close) | `quantity` × (1 − `price`) − fee, to the cent half up, at the fill price. |

Use decimal arithmetic, not binary floating point: in a float, 0.655 × 1000 is not exactly 655, and a half-cent tie can
round the wrong way. The calculator below uses Python's `decimal`, and integer micro-dollars in TypeScript (`bigint`) and
Go (`int64`).

## Check your implementation

Run your code on these orders and compare to the cent. Each came from Splash's own fee code.

| `theta` | Order | Contracts cost | Fee | Debit | Credit on fill |
| - | - | - | - | - | - |
| 0.07 | Buy: `yes`, 0.59 × 10 | 5.90 | 0.17 | 6.07 | |
| 0.07 | Buy: `yes`, 0.50 × 6 | 3.00 | 0.10 | 3.10 | |
| 0.07 | Buy: `no`, 0.38 × 25 | 9.50 | 0.41 | 9.91 | |
| 0.07 | Buy: `no`, 0.655 × 1000 | 655.00 | 15.82 | 670.82 | |
| 0.10 | Buy: `no`, 0.655 × 13 | 8.515 | 0.29 | 8.81 | |
| 0.07 | Close: `no`, 0.34 × 10 | | 0.16 | 0 | 6.44 |

The second row is a half-to-even tie: 0.07 × 6 × 0.5 × 0.5 = $0.105, which rounds to $0.10, not $0.11. The fifth row
rounds the debit half up: $8.515 + $0.29 = $8.805, debited as $8.81. The last row sells 10 YES contracts at $0.66,
filled at that price.

## A cost calculator

`order_cost` / `orderCost` / `OrderCost` takes the `fee` object from `GET /account` as it arrives, the order's `price`
and `quantity`, and whether the order closes a position you hold. It returns the contracts cost, the fee, the debit and,
for a close, the credit. `can_afford` / `canAfford` / `CanAfford` checks the debit against `cash_available` and the
contracts cost against `max_trade`. The Python and TypeScript tabs then read the two values and place the order, reusing
the request helpers from [the signed-order example](https://docs.splashsports.com/predictions/authentication.md#example-place-a-signed-order); the Go
tab is a complete program with the values filled in by hand.

```bash cURL theme={null}
# The arithmetic needs decimal maths, so it is in the other tabs. These are the two reads it starts from.
# Reuses KEY and SECRET from the signed-order example.
for URLPATH in /predictions-service/api/account /predictions-service/api/balance; do
  TS=$(( $(date +%s) * 1000 ))
  SIG=$(printf '%s' "${TS}GET${URLPATH}" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
  curl -s "https://api.splashsports.com$URLPATH" \
    -H "SPLASH-ACCESS-KEY: $KEY" \
    -H "SPLASH-ACCESS-TIMESTAMP: $TS" \
    -H "SPLASH-ACCESS-SIGNATURE: $SIG"
  echo
done
```

```python Python theme={null}
# Reuses request() (and sign()) from the signed-order example in Authentication.
from decimal import Decimal, ROUND_FLOOR, ROUND_HALF_EVEN, ROUND_HALF_UP

MICRO = Decimal("0.000001")
CENT = Decimal("0.01")


def d(value):
    # Through str() so 0.655 is exactly 0.655, not the nearest binary float.
    return Decimal(str(value))


def cents(value):
    # Half up to the cent; ties go toward plus infinity, also for a negative amount.
    return (value + CENT / 2).quantize(CENT, rounding=ROUND_FLOOR)


def mul6(a, b):
    # Each multiplication step is rounded to 6 decimal places, half up, as Splash does.
    return (a * b).quantize(MICRO, rounding=ROUND_HALF_UP)


def fee_amount(fee, price, quantity):
    """The fee in dollars for one order: theta x C x p x (1 - p), from the `fee` object on GET /account."""
    if fee["type"] != "formula":
        raise ValueError("unknown fee type: " + fee["type"])
    p = d(price)
    raw = mul6(mul6(mul6(d(fee["theta"]), d(quantity)), p), Decimal(1) - p)
    return raw.quantize(CENT, rounding=ROUND_HALF_EVEN)


def order_cost(fee, price, quantity, closing=False):
    """What an order costs. Set closing=True for an order that sells contracts you hold."""
    p, qty = d(price), d(quantity)
    fee_dollars = fee_amount(fee, price, quantity)
    if closing:
        # A close debits nothing; the proceeds are credited when it fills.
        return {"contracts_cost": Decimal(0), "fee": fee_dollars, "debit": Decimal(0),
                "proceeds": cents(qty * (Decimal(1) - p) - fee_dollars)}
    contracts_cost = mul6(p, qty)
    return {"contracts_cost": contracts_cost, "fee": fee_dollars, "debit": cents(contracts_cost + fee_dollars)}


def can_afford(cost, cash_available, max_trade):
    """True when the debit fits in cash_available and the contracts cost fits in max_trade."""
    return cost["debit"] <= d(cash_available) and cost["contracts_cost"] <= d(max_trade)


account = request("GET", "/predictions-service/api/account").json()
cash_available = request("GET", "/predictions-service/api/balance").json()["balance"]["cash_available"]

order = {
    "market_id": "pm_6ae1e3c09e1d4c7a2b5f80e3d6a9c412",
    "side": "yes",
    "quantity": 10,
    "price": 0.59,
    "time_in_force": "FOK",
    "request_id": "3f1c2a9e-5b7d-4e0a-9c1d-8a6b2e4f7d10",
}
cost = order_cost(account["fee"], order["price"], order["quantity"])
print("fee", cost["fee"], "debit", cost["debit"])  # theta 0.07: fee 0.17 debit 6.07
if can_afford(cost, cash_available, account["max_trade"]):
    # The raw order, with no fee field: Splash computes the fee itself.
    response = request("POST", "/predictions-service/api/trading/orders", order)
    print(response.status_code, response.json())
```

```typescript TypeScript theme={null}
// Reuses KEY, SECRET, HOST and sign() from the signed-order example in Authentication.
// All money is bigint micro-dollars (1 dollar = 1_000_000n), so nothing is lost to binary floats.
type Fee = { type: "formula"; formula_type: string; theta: number };

const ONE = 1_000_000n; // $1 in micro-dollars
const CENT = 10_000n; // $0.01

// Exact for any value with at most 6 decimal places, which every price and theta here has.
const micro = (value: number): bigint => BigInt(Math.round(value * 1_000_000));
// Rounds toward minus infinity; bigint division alone truncates toward zero.
const floorDiv = (a: bigint, b: bigint): bigint => (a >= 0n ? a / b : -((-a + b - 1n) / b));
// a and b are both micro-scaled; the product is rounded to 6 decimal places, half up, as Splash does.
const mul6 = (a: bigint, b: bigint): bigint => floorDiv(a * b + ONE / 2n, ONE);
const roundHalfUp = (value: bigint, unit: bigint): bigint => floorDiv(value + unit / 2n, unit) * unit;
const roundHalfEven = (value: bigint, unit: bigint): bigint => {
  const whole = floorDiv(value, unit);
  const rest = value - whole * unit;
  const up = rest * 2n > unit || (rest * 2n === unit && whole % 2n !== 0n);
  return (up ? whole + 1n : whole) * unit;
};

/** The fee in micro-dollars for one order: theta x C x p x (1 - p), from the `fee` object on GET /account. */
function feeAmount(fee: Fee, price: number, quantity: number): bigint {
  if (fee.type !== "formula") {
    throw new Error("unknown fee type");
  }
  const p = micro(price);
  return roundHalfEven(mul6(mul6(micro(fee.theta) * BigInt(quantity), p), ONE - p), CENT);
}

type OrderCost = { contractsCost: bigint; fee: bigint; debit: bigint; proceeds?: bigint };

/** What an order costs, in micro-dollars. Pass closing = true for an order that sells contracts you hold. */
function orderCost(fee: Fee, price: number, quantity: number, closing = false): OrderCost {
  const p = micro(price);
  const qty = BigInt(quantity);
  const feeMicro = feeAmount(fee, price, quantity);
  if (closing) {
    // A close debits nothing; the proceeds are credited when it fills.
    return { contractsCost: 0n, fee: feeMicro, debit: 0n, proceeds: roundHalfUp((ONE - p) * qty - feeMicro, CENT) };
  }
  const contractsCost = p * qty;
  return { contractsCost, fee: feeMicro, debit: roundHalfUp(contractsCost + feeMicro, CENT) };
}

/** True when the debit fits in cash_available and the contracts cost fits in max_trade. */
function canAfford(cost: OrderCost, cashAvailable: number, maxTrade: number): boolean {
  return cost.debit <= micro(cashAvailable) && cost.contractsCost <= micro(maxTrade);
}

/** Micro-dollars as a dollar string, for display: 6070000n -> "6.07". */
const dollars = (value: bigint): string => {
  const magnitude = value < 0n ? -value : value;
  return `${value < 0n ? "-" : ""}${magnitude / ONE}.${((magnitude % ONE) / CENT).toString().padStart(2, "0")}`;
};

async function signedJson(method: string, path: string, body = ""): Promise<any> {
  const timestamp = Date.now().toString();
  const response = await fetch(HOST + path, {
    method,
    headers: {
      ...(body ? { "Content-Type": "application/json" } : {}),
      "SPLASH-ACCESS-KEY": KEY,
      "SPLASH-ACCESS-TIMESTAMP": timestamp,
      "SPLASH-ACCESS-SIGNATURE": sign(SECRET, timestamp, method, path, body),
    },
    body: body || undefined,
  });
  return response.json();
}

const account = await signedJson("GET", "/predictions-service/api/account");
const { balance } = await signedJson("GET", "/predictions-service/api/balance");

const order = {
  market_id: "pm_6ae1e3c09e1d4c7a2b5f80e3d6a9c412",
  side: "yes",
  quantity: 10,
  price: 0.59,
  time_in_force: "FOK",
  request_id: "3f1c2a9e-5b7d-4e0a-9c1d-8a6b2e4f7d10",
};
const cost = orderCost(account.fee, order.price, order.quantity);
console.log("fee", dollars(cost.fee), "debit", dollars(cost.debit)); // theta 0.07: fee 0.17 debit 6.07
if (canAfford(cost, balance.cash_available, account.max_trade)) {
  // The raw order, with no fee field: Splash computes the fee itself.
  console.log(await signedJson("POST", "/predictions-service/api/trading/orders", JSON.stringify(order)));
}
```

```go Go theme={null}
package main

import (
	"encoding/json"
	"fmt"
	"math"
)

// Fee is the `fee` object from GET /account.
type Fee struct {
	Type        string  `json:"type"`
	FormulaType string  `json:"formula_type"`
	Theta       float64 `json:"theta"`
}

// All money is int64 micro-dollars (1 dollar = 1_000_000), so nothing is lost to binary floats.
const (
	one  int64 = 1_000_000 // $1
	cent int64 = 10_000    // $0.01
)

// Exact for any value with at most 6 decimal places, which every price and theta here has.
func micro(v float64) int64 { return int64(math.Round(v * 1_000_000)) }

// floorDiv rounds toward minus infinity; Go's / truncates toward zero.
func floorDiv(a, b int64) int64 {
	q := a / b
	if a%b != 0 && (a < 0) != (b < 0) {
		q--
	}
	return q
}

// a and b are both micro-scaled; the product is rounded to 6 decimal places, half up, as Splash does.
func mul6(a, b int64) int64           { return floorDiv(a*b+one/2, one) }
func roundHalfUp(v, unit int64) int64 { return floorDiv(v+unit/2, unit) * unit }

func roundHalfEven(v, unit int64) int64 {
	whole := floorDiv(v, unit)
	rest := v - whole*unit
	if rest*2 > unit || (rest*2 == unit && whole%2 != 0) {
		whole++
	}
	return whole * unit
}

// FeeAmount is the fee in micro-dollars for one order: theta x C x p x (1 - p), rounded to the cent half to even.
func FeeAmount(fee Fee, price float64, quantity int64) (int64, error) {
	if fee.Type != "formula" {
		return 0, fmt.Errorf("unknown fee type %q", fee.Type)
	}
	p := micro(price)
	return roundHalfEven(mul6(mul6(micro(fee.Theta)*quantity, p), one-p), cent), nil
}

// Cost is what one order costs, in micro-dollars.
type Cost struct {
	ContractsCost int64 // quantity x price; 0 on a close
	Fee           int64
	Debit         int64 // what a buy takes from cash_available; 0 on a close
	Proceeds      int64 // what a close credits when it fills; 0 on a buy
}

// OrderCost prices one order. Pass closing = true for an order that sells contracts you hold.
func OrderCost(fee Fee, price float64, quantity int64, closing bool) (Cost, error) {
	p := micro(price)
	feeMicro, err := FeeAmount(fee, price, quantity)
	if err != nil {
		return Cost{}, err
	}
	if closing {
		// A close debits nothing; the proceeds are credited when it fills.
		return Cost{Fee: feeMicro, Proceeds: roundHalfUp((one-p)*quantity-feeMicro, cent)}, nil
	}
	contractsCost := p * quantity
	return Cost{ContractsCost: contractsCost, Fee: feeMicro, Debit: roundHalfUp(contractsCost+feeMicro, cent)}, nil
}

// CanAfford is true when the debit fits in cash_available and the contracts cost fits in max_trade.
func CanAfford(cost Cost, cashAvailable, maxTrade float64) bool {
	return cost.Debit <= micro(cashAvailable) && cost.ContractsCost <= micro(maxTrade)
}

// dollars formats a whole-cent amount of micro-dollars: 6_070_000 -> "6.07".
func dollars(v int64) string {
	sign := ""
	if v < 0 {
		sign, v = "-", -v
	}
	return fmt.Sprintf("%s%d.%02d", sign, v/one, v%one/cent)
}

func main() {
	// `fee` and `max_trade` come from GET /account and `cash_available` from GET /balance, read with the
	// signed GET in Authentication. Here they are filled in by hand.
	var fee Fee
	if err := json.Unmarshal([]byte(`{"type":"formula","formula_type":"symmetric_price_uncertainty","theta":0.07}`), &fee); err != nil {
		panic(err)
	}
	maxTrade, cashAvailable := 100000.0, 250.0

	cost, err := OrderCost(fee, 0.59, 10, false)
	if err != nil {
		panic(err)
	}
	fmt.Println("fee", dollars(cost.Fee), "debit", dollars(cost.Debit)) // fee 0.17 debit 6.07
	if CanAfford(cost, cashAvailable, maxTrade) {
		// Send the raw order to POST /trading/orders: market_id, side, quantity, price, time_in_force and
		// request_id. There is no fee field; Splash computes the fee itself.
		fmt.Println("enough cash: send the order")
	}
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.