roboquant avatar

Broker

What is a broker

A broker facilitates the buying and selling transactions of financial assets, such as stocks, bonds, currencies, options, and other securities, on behalf of their clients. Brokers may operate through different platforms, including online trading platforms.

In _roboquant, it is the broker that receives new orders and processes them. The outcome of this processing is an updated Account object. This object is the same for all broker implementations. This allows for a seamless transition between the 4 stages of developing a strategy.

There are two types of broker implementations:

  1. SimBroker: simulates a broker and the trading that happens on an exchange.

  2. Third party brokers: implementations that integrate with a real broker like IBKR, Alpaca or Binance.

These two types implement the same Broker interface. Normally during back-testing and forward-testing you’ll use the SimBroker, and during paper-trading and live-trading you’ll use a real broker. The main method is broker.place. And although normally you wouldn’t invoke it directly since the Roboquant instance would do that as part of a run, you can if you want to:

val orders = listOf(
    MarketOrder(Asset("AAPL"), 100), // BUY 100 stocks Apple
    MarketOrder(Asset("TSLA"), -100), // SELL 100 stocks Tesla
)
broker.place(orders)

Normally you would specify the broker you want to use when you create an instance of `Roboquant'

val roboquant = Roboquant(EMAStrategy(), AccountMetric(), broker = BinanceBroker())
println(roboquant.broker.account.fullSummary())
Broker implementations can differ in what order types and assets are supported.So when moving from the SimBroker to a real broker, always validate if the behavior is still as expected.

Account

broker.account property returns an Account object. This Account object contains a snapshot of the current state and is identical between the simulated broker and real brokers. An account instance is guaranteed not to change, which also implies that if you want an updated snapshot, you need to invoke broker.account again.

The Account object contains the following key properties:

  • The base currency of the account, used when converting a Wallet into a single-currency Amount

  • The total cash balance (represents the credit cash - loan value)

  • All the open positions

  • The trades that contain all the trade executions that have happened so far

  • The orders, both open orders and closed orders

  • The total amount of buying power available for trading (denoted in the base currency of the account)

Always use the Account.buyingPower property if you want to know how much money is remaining for trading. Especially when using a MarginAccount, you shouldn’t rely on the Account.cash property, since that doesn’t take into account any margin or leverage you might have.

Since properties like trades and openOrders of the account object are plain Kotlin collections, you can use standard Kotlin access patterns. Kotlin’s collection APIs are a very powerful tool to access data structures once you learn them.

If you are new to Kotlin’s collections, you can go to this page for more info: https://kotlinlang.org/docs/collections-overview.html

Additionally, roboquant defines several extensions that come in handy when dealing with the account object.

val account = broker.account

// Print the account
println(account.summary())
println(account.fullSummary())

// Standard Kotlin
val winningTrades = account.trades.filter { it.pnl > 0 }
val loosingTrades = account.trades.filter { it.pnl < 0 }
val biggestExposure = account.positions.maxBy { it.exposure.value }
val marketOrders = account.openOrders.filter { it.order is MarketOrder }
val appleTrades = account.trades.filter { it.asset.symbol == "AAPL" }

// Some useful extensions
val totalUnrealizedPNL = account.positions.unrealizedPNL
val totalRealizedPNL = account.trades.realizedPNL
val totalRealizedLosses = loosingTrades.realizedPNL
val biggestLoosingTrade = account.trades.map { it.pnl.convert(Currency.USD) }.minBy { it.value }
val totalFee = account.trades.fee
val realizedPNLApple = appleTrades.realizedPNL
val cancellationOrders = account.openOrders.cancel()

// More advanced examples
val zone = ZoneId.systemDefault()
val realizedPNLPerDayOfWeek = account.trades
    .groupBy { it.time.atZone(zone).get(ChronoField.DAY_OF_WEEK) }
    .mapValues { it.value.realizedPNL }
val maxFeeExchange = account.trades
    .groupBy { it.asset.exchange }
    .mapValues { it.value.fee.convert(Currency.USD) }
    .maxBy { it.value.value }

A common case of accessing the account object is if you implement your own policy. The following example provides some typical patterns that you can use in your policy using attributes found in the account object:

for (signal in signals) {
    val asset = signal.asset
    val openOrders = account.openOrders.filter { it.asset == asset }
    val openPositions = account.positions.filter { it.asset == asset } // returns 0 or 1 positions
    val pastTrades = account.trades.filter { it.asset == asset }

    // cancel open orders
    val cancellations = openOrders.map { CancelOrder(it) }

    // close positions
    val closeOrders = openPositions.map { MarketOrder(it.asset, -it.size) }

    // Stop a losing streak
    val last2Hours = Timeframe.past(2.hours)
    val pnl = pastTrades.filter { last2Hours.contains(it.time) }.sumOf { it.pnlValue }
    if (pnl < -1_000) {
        TODO()
    }
}

As shown in the code snippet above, you can print an account.summary or account.fullSummary overview. The full summary includes all positions, trades and orders, so it can become very large after an extensive back test:

account
├── last update: 2023-01-09T14:47:01Z
├── base currency: BUSD
├── cash: BUSD 199374.52
├── buying power: BUSD 199374.52
├── equity: BUSD 999175.51
├── position summary
│   ├── open: 4
│   ├── long: 4
│   ├── short: 0
│   ├── total value: BUSD 799800.99
│   ├── long value: BUSD 799800.99
│   ├── short value: BUSD 0.00
│   └── unrealized p&l: BUSD 1085.35
├── trade summary
│   ├── total: 20
│   ├── realized p&l: BUSD -1909.83
│   ├── fee:
│   ├── first: 2023-01-09T14:03:00.170064Z
│   ├── last: 2023-01-09T14:43:00.149166Z
│   ├── winners: 2
│   └── loosers: 6
├── order summary
│   ├── open: 0
│   ├── closed: 20
│   ├── completed: 20
│   ├── cancelled: 0
│   ├── expired: 0
│   └── rejected: 0
├── cash
│   ├──   ccy│    amount│
│   └──  BUSD│ 199374.52│
├── positions
│   ├──   symbol│  ccy│       size│ entry price│ mkt price│ mkt value│ unrlzd p&l│
│   ├──  XRPBUSD│ BUSD│  567419.08│        0.35│      0.35│ 200128.71│     433.97│
│   ├──  BNBBUSD│ BUSD│     714.96│      279.33│    279.80│ 200045.81│     337.51│
│   ├──  TRXBUSD│ BUSD│ 3630666.41│        0.05│      0.06│ 199831.88│     270.50│
│   └──  ETHBUSD│ BUSD│     150.81│     1324.52│   1324.81│ 199794.60│      43.37│
├── open orders
│   └── EMPTY
├── closed orders
│   ├──   type│  symbol│  ccy│    status│  id│            opened at│            closed at│                  details│
│   ├── MARKET│ TRXBUSD│ BUSD│ COMPLETED│ 336│ 2023-01-09T14:02:00Z│ 2023-01-09T14:03:00Z│  size=3640997.63 tif=GTC│
│   ├── MARKET│ XRPBUSD│ BUSD│ COMPLETED│ 337│ 2023-01-09T14:03:00Z│ 2023-01-09T14:04:00Z│   size=571265.35 tif=GTC│
│   ├── MARKET│ BNBBUSD│ BUSD│ COMPLETED│ 338│ 2023-01-09T14:04:00Z│ 2023-01-09T14:05:00Z│      size=719.66 tif=GTC│
│   ├── MARKET│ BNBBUSD│ BUSD│ COMPLETED│ 339│ 2023-01-09T14:06:00Z│ 2023-01-09T14:07:00Z│     size=-719.66 tif=GTC│
│   ├── MARKET│ XRPBUSD│ BUSD│ COMPLETED│ 340│ 2023-01-09T14:06:00Z│ 2023-01-09T14:07:00Z│  size=-571265.35 tif=GTC│
│   ├── MARKET│ TRXBUSD│ BUSD│ COMPLETED│ 341│ 2023-01-09T14:06:01Z│ 2023-01-09T14:07:00Z│ size=-3640997.63 tif=GTC│
│   ├── MARKET│ ETHBUSD│ BUSD│ COMPLETED│ 342│ 2023-01-09T14:07:00Z│ 2023-01-09T14:08:00Z│     size=-151.51 tif=GTC│
│   ├── MARKET│ BTCBUSD│ BUSD│ COMPLETED│ 343│ 2023-01-09T14:07:00Z│ 2023-01-09T14:08:00Z│      size=-11.59 tif=GTC│
│   ├── MARKET│ ETHBUSD│ BUSD│ COMPLETED│ 344│ 2023-01-09T14:11:00Z│ 2023-01-09T14:12:00Z│      size=151.51 tif=GTC│
│   ├── MARKET│ BTCBUSD│ BUSD│ COMPLETED│ 345│ 2023-01-09T14:11:00Z│ 2023-01-09T14:12:00Z│       size=11.59 tif=GTC│
│   ├── MARKET│ BNBBUSD│ BUSD│ COMPLETED│ 346│ 2023-01-09T14:12:00Z│ 2023-01-09T14:13:00Z│      size=717.63 tif=GTC│
│   ├── MARKET│ XRPBUSD│ BUSD│ COMPLETED│ 347│ 2023-01-09T14:12:00Z│ 2023-01-09T14:13:00Z│   size=569823.99 tif=GTC│
│   ├── MARKET│ TRXBUSD│ BUSD│ COMPLETED│ 348│ 2023-01-09T14:18:01Z│ 2023-01-09T14:19:00Z│  size=3637915.47 tif=GTC│
│   ├── MARKET│ TRXBUSD│ BUSD│ COMPLETED│ 349│ 2023-01-09T14:36:01Z│ 2023-01-09T14:37:00Z│ size=-3637915.47 tif=GTC│
│   ├── MARKET│ XRPBUSD│ BUSD│ COMPLETED│ 350│ 2023-01-09T14:37:00Z│ 2023-01-09T14:38:00Z│  size=-569823.99 tif=GTC│
│   ├── MARKET│ BNBBUSD│ BUSD│ COMPLETED│ 351│ 2023-01-09T14:38:00Z│ 2023-01-09T14:39:00Z│     size=-717.63 tif=GTC│
│   ├── MARKET│ XRPBUSD│ BUSD│ COMPLETED│ 352│ 2023-01-09T14:40:00Z│ 2023-01-09T14:41:00Z│   size=567419.08 tif=GTC│
│   ├── MARKET│ BNBBUSD│ BUSD│ COMPLETED│ 353│ 2023-01-09T14:41:00Z│ 2023-01-09T14:42:00Z│      size=714.96 tif=GTC│
│   ├── MARKET│ TRXBUSD│ BUSD│ COMPLETED│ 354│ 2023-01-09T14:41:01Z│ 2023-01-09T14:42:00Z│  size=3630666.41 tif=GTC│
│   └── MARKET│ ETHBUSD│ BUSD│ COMPLETED│ 355│ 2023-01-09T14:42:00Z│ 2023-01-09T14:43:00Z│      size=150.81 tif=GTC│
└── trades
    ├──                  time│  symbol│  ccy│        size│       cost│  fee│ rlzd p&l│    price│
    ├──  2023-01-09T14:03:00Z│ TRXBUSD│ BUSD│  3640997.63│  199983.59│ 0.00│     0.00│     0.05│
    ├──  2023-01-09T14:04:00Z│ XRPBUSD│ BUSD│   571265.35│  200191.40│ 0.00│     0.00│     0.35│
    ├──  2023-01-09T14:05:00Z│ BNBBUSD│ BUSD│      719.66│  199869.57│ 0.00│     0.00│   277.73│
    ├──  2023-01-09T14:07:00Z│ BNBBUSD│ BUSD│     -719.66│ -199613.72│ 0.00│  -255.85│   277.37│
    ├──  2023-01-09T14:07:00Z│ XRPBUSD│ BUSD│  -571265.35│ -199294.55│ 0.00│  -896.85│     0.35│
    ├──  2023-01-09T14:07:00Z│ TRXBUSD│ BUSD│ -3640997.63│ -199361.09│ 0.00│  -622.49│     0.05│
    ├──  2023-01-09T14:08:00Z│ ETHBUSD│ BUSD│     -151.51│ -199576.28│ 0.00│     0.00│  1317.25│
    ├──  2023-01-09T14:08:00Z│ BTCBUSD│ BUSD│      -11.59│ -199515.11│ 0.00│     0.00│ 17214.42│
    ├──  2023-01-09T14:12:00Z│ ETHBUSD│ BUSD│      151.51│  200113.21│ 0.00│  -536.92│  1320.79│
    ├──  2023-01-09T14:12:00Z│ BTCBUSD│ BUSD│       11.59│  199726.10│ 0.00│  -210.99│ 17232.62│
    ├──  2023-01-09T14:13:00Z│ BNBBUSD│ BUSD│      717.63│  199449.32│ 0.00│     0.00│   277.93│
    ├──  2023-01-09T14:13:00Z│ XRPBUSD│ BUSD│   569823.99│  199629.30│ 0.00│     0.00│     0.35│
    ├──  2023-01-09T14:19:00Z│ TRXBUSD│ BUSD│  3637915.47│  199814.30│ 0.00│     0.00│     0.05│
    ├──  2023-01-09T14:37:00Z│ TRXBUSD│ BUSD│ -3637915.47│ -199519.71│ 0.00│  -294.59│     0.05│
    ├──  2023-01-09T14:38:00Z│ XRPBUSD│ BUSD│  -569823.99│ -199931.24│ 0.00│   301.94│     0.35│
    ├──  2023-01-09T14:39:00Z│ BNBBUSD│ BUSD│     -717.63│ -200055.24│ 0.00│   605.92│   278.77│
    ├──  2023-01-09T14:41:00Z│ XRPBUSD│ BUSD│   567419.08│  199694.74│ 0.00│     0.00│     0.35│
    ├──  2023-01-09T14:42:00Z│ BNBBUSD│ BUSD│      714.96│  199708.30│ 0.00│     0.00│   279.33│
    ├──  2023-01-09T14:42:00Z│ TRXBUSD│ BUSD│  3630666.41│  199561.38│ 0.00│     0.00│     0.05│
    └──  2023-01-09T14:43:00Z│ ETHBUSD│ BUSD│      150.81│  199751.23│ 0.00│     0.00│  1324.52│ 

Position

Normally, you won’t create positions since this is all taken off by the broker implementation. However, it might be helpful to understand what attributes a position contains and how they are used.

Below is an example of a position in Tesla stocks:

val asset = Asset("TSLA")
// We bought 5 stocks tesla
// We bought at $250.10
// The current market price is $250.05
val position = Position(asset, Size(5), 250.10, 250.05, Instant.now())

println(position.unrealizedPNL) // USD -0.25
println(position.marketValue) // USD 1,250.25
println(position.totalCost) // USD 1,250.50

But the same attributes and logic apply to Forex, crypto, and other asset classes:

val asset = Asset("BTC-USDT", AssetType.CRYPTO, "USDT", "CRYPTO")

// We bought 0.1 bitcoin
// We bought at 25.100 USDT
// The current market price is 25.000 USDT (for 1 bitcoin)
val position = Position(asset, Size(0.1), 25_100.0, 25_000.0, Instant.now())

println(position.unrealizedPNL) // USDT -10.00
println(position.marketValue) // USDT 2,500.00
println(position.totalCost) // USDT 2,510.00
Equity

Equity is a key metric to monitor if you want to see how your strategy is performing during a run. The following two equations hold true when running a back test:

// Equity property
val equity0 = account.equity

// Sum of cash balances + open positions
val equity1 = account.cash + account.positions.marketValue

// Sum of initial deposit + all the Profit And Loss
val equity2 = initialDeposit + account.trades.realizedPNL + account.positions.unrealizedPNL

assert(equity0 == equity1)
assert(equity0 == equity2)

The metric AccountMetric makes the equity curve available for further analysis.

When you plot the equity curve in a Jupyter Notebook, you don’t only see the PNL performance, you also can see how smooth the curve is. This provides a visual indication of the volatility of the returns of your strategy.

Out of the box available brokers

The following Broker implementations are available out-of-the-box:

brokers

See also SimBroker and Integration for more details on how to use these brokers.