Domain Model

Approach

Besides core-components like Strategy and Policy, roboquant also comes with a number of domain specific classes that make it easier to implement robust trading strategies. The two main focus areas are money related entities and time related entities.

This approach makes it very easy to deal with these entities without much code. This is also where many of the Kotlin language features come in handy with support for operator overloading and extension functions.

Money

In order to make it easier to develop solutions in a multi-currency setup, roboquant introduces a number of money-related domain classes: Currency, Amount, Wallet and ExchangeRates that will help.

Currency

The roboquant Currency class represents any type of currency. It follows closely the interface that comes with the standard Java Currency class, but is independent of it. The main reason is that the Java implementation doesn’t support cryptocurrencies while the roboquant implementation supports any type of currency.

Also, several common currencies are predefined, to make it easier to work with.

Currency.USD
Currency.BTC
Currency.getInstance("MyCryptoCoin")

Amount

An amount can hold the value for a single currency. You can create a new Amount using the constructor. But you can also use the extension functions that are defined commonly used currencies (f.e. 120.EUR). Amounts are just like numbers immutable.

val amount1 = Amount(Currency.getInstance("USD"), 100.0) // the most code
val amount2 = Amount(Currency.USD, 100.0) // less code
val amount3 = 100.USD // the least code

val amount4 = 50.USD * 2 // simple calculation
require(amount4 == amount3)

// display using the common number of decimals for that currency
println(amount1.formatValue())
Amounts store their values internally as a Double and not a BigDecimal. For (simulated) trading the potential loss of precision is not an issue and the benefits are improved performance and lower memory consumption.

Wallet

A Wallet can hold amounts of multiple currencies at the same time. You can create a new Wallet by using its constructor, or by adding multiple amounts together. You can also deposit and withdraw amounts in a wallet. A Wallet instance is mutable.

There is an important difference between deposit method and using the + operator. The deposit method will modify the existing instance while the + operator will return a new wallet instance.

val wallet1 = Wallet(100.EUR, 10.USD)
wallet1.deposit(200.GBP)
wallet1.withdraw(1000.JPY)

val wallet2 = 20.EUR - 1000.JPY + 100.GBP
val wallet3 = 0.02.BTC + 100.USDT + 0.1.ETH
If you add two amounts together you’ll always get a new Wallet instance back, even if the two amounts have the same currency.

ExchangeRates

ExchangeRates is an interface that supports the conversion of an amount from one currency to another one. Although you can invoke the API of an implementation directly, typically you would use the convert method of an Amount or Wallet instance.

The Config.exchangeRates property is used by the components to perform the actual conversion. So you can change this property and all code from now on will use this new implementation.

The ExchangeRates interface also supports different rates at different moments in time, but it depends on the actual implementation if the provided time parameter is taken into consideration. One of the implementations that comes out of the box with roboquant, is the ECBExchangeRates which contains daily exchange rates for most fiat currencies from the year 2000 onwards.

// Load the exchange rates as published by the ECB (European Central Bank)
Config.exchangeRates = ECBExchangeRates.fromWeb()
val amountEUR = 20.EUR
val amountUSD = amountEUR.convert(Currency.USD)

val wallet = 20.USD + 1000.JPY

// Convert wallet based on today's exchange rates
val amountGBP1 = wallet.convert(Currency.GBP)

// Convert wallet based on two years ago exchange rates
val time = Instant.now() - 2.years
val amountGBP2 = wallet.convert(Currency.GBP, time)
require(amountGBP1 != amountGBP2)

Time

In order to make it easier to trade in a multi-regions, roboquant introduces a number of domain classes: Timeline, Timeframe, ZonedPeriod.

Internally all time in stored in Instant type and so can always be easily compared, regardless from which timezone the information originated from. The Event for example exposes its time attribute as an Instant.

Timeline

Timeline is just a list of Instant instances in chronological order. For example a historical feed has a timeline, which represents all the moments in time that the feed has data available. A timeline has zero or more elements, but is always finite.

Timeframe

Timeframe represents a period of time with a start-time (inclusive) and an end-time (exclusive).

It is used throughout roboquant, but one particular use-case is that you can restrict a run to a particular timeframe. So instead of one large run over all the events in a Feed, you can have multiple smaller runs. The Timeframe has several helper methods that makes it easier to split a timeframe into smaller parts.

// Parse a string
val tf1 = Timeframe.parse("2019-01-01", "2020-01-01")

// Add to years to the timeframe
val tf2 = tf1 + 2.years

// Split timeframe in 31 days periods
val tf3 = tf2.split(31.days) // result is List<Timeframe>

// Use a predefined timeframes
val tf4 = Timeframe.financialCrisis2008

ZonedPeriod

When dealing with time duration, there are actual two types of duration in Java/Kotlin:

  1. Period which you use for days and longer and is timezone sensitive

  2. Duration which you use for days and shorted and is not timezone sensitive

The ZonedPeriod in roboquant makes this a single implementation.