val strategy = EMAStrategy()
val roboquant = Roboquant(strategy)
Roboquant
Create an Instance
The Roboquant
class (org.roboquant.Roboquant) is the engine of the platform, not to be confused with roboquant (all lower case) which is the name of the platform itself. An instance of the Roboquant
class orchestrates the interaction between the components and performs the actual runs.
The only required parameter when creating a new instance of Roboquant
is the strategy. So the bare minimum to create an instance would look something like this:
The following default values will be used if you don’t provide additional parameters:
-
No
Metric
will be captured -
The
SimBroker
will be used as the broker with its default settings -
The
FlexPolicy
will be the policy used -
The
MemoryLogger
for logging the metrics (although without any metrics to capture, there is not much to log)
Each of these defaults can be overwritten with a different implementation when you instantiate a Roboquant
, as the following code snippet demonstrates:
val roboquant = Roboquant(
EMAStrategy(),
AccountMetric(), PNLMetric(),
policy = FlexPolicy {
shorting = true
},
broker = AlpacaBroker(),
logger = InfoLogger()
)
As you can see, you can provide multiple metrics, but only a single instance of the other components. But that doesn’t mean you cannot use multiple strategies or policies. You can, for example, combine multiple strategies into one and pass that instance to the Roboquant
constructor:
val strategy1 = EMAStrategy.PERIODS_12_26
val strategy2 = EMAStrategy.PERIODS_50_200
val strategy = CombinedStrategy(strategy1, strategy2)
val roboquant = Roboquant(strategy)
See also the strategy page for more details.
Run
After you have created an instance of the Roboquant
class and have a data feed, you can start the run.
The same run method is used for all the different stages, from back testing to live trading. See also the 4 stages for more details about these stages.
|
In the most basic form, you provide the Feed
as a single the argument to the run
method. In that case, all the events available in the feed will be used in the run. So if your feed contains 100 stocks with each stock having 20 years of end-of-day candlesticks, all that data will be used in a single run.
roboquant.run(feed)
However, you can restrict a run to a certain timeframe. This is especially useful for live feeds, that would otherwise run forever. But it also comes in handy when using historic data, and you want to perform multiple runs over different timeframes.
// Historical feed run example
val timeframe = Timeframe.fromYears(2015, 2020)
roboquant.run(feed1, timeframe)
// Live feed run example
val timeframe2 = Timeframe.next(120.minutes)
roboquant.run(feed2, timeframe2)
You can invoke a run multiple times, for example, with different timeframes. The following code shows how to use this to perform a walk-forward back test of two-year periods:
val timeframe = feed.timeframe
timeframe.split(2.years).forEach {
roboquant.run(feed, it)
println(roboquant.broker.account.equityAmount)
}
Or run even more back tests, like in this Monte Carlo simulation where we draw 100 random timeframes of 2 years each:
val timeframe = feed.timeframe
timeframe.sample(2.years, 100).forEach {
roboquant.run(feed, it)
println(roboquant.broker.account.equityAmount)
}
Validation run
There are strategies that are self-learning. For example, many machine-learning-based strategies fall under this category. These strategies are trained on one timeframe and then validated on another (typically sequential) timeframe.
Also, this type of strategy can be easily back-tested with roboquant. The run method accepts an additional timeframe parameter to trigger a second validation phase.
// Single run example
val (main, validation) = feed.timeframe.splitTrainTest(0.2) // 20% for validation phase
roboquant.run(feed, main, "run-main")
println(roboquant.broker.account.equityAmount)
roboquant.broker.reset()
roboquant.run(feed, validation, "run-validation")
// Walk forward example
feed.timeframe.split(2.years).forEach {
val (main2, validation2) = it.splitTrainTest(0.25) // 25% for validation phase
roboquant.run(feed, main2)
roboquant.broker.reset()
roboquant.run(feed, validation, "run-validation")
println(roboquant.broker.account.equityAmount)
}
From a metric logging perspective, the metrics have a different RunPhase
in the RunInfo
object associated with them. So you can always find out in which RunPhase
a metric was generated.
Running in Parallel
If you want to run many back-tests, you can run them in parallel and leverage the available cores on your computer to expedite the process. The roboquant engine scales almost linearly with the number of available cores, and it only requires a few extra lines of code.
Performance and scalability are determined by many other factors besides the engine itself. For example, the amount of memory used and allocated at each step, the CPU usage of your strategy, or the files read from disc. |
The following example shows how to run a walk forward in parallel. By reusing the same logger instance, all results will be stored in a single logger instance and can be easily compared afterwards. Also, the feed can be shared across jobs, resulting in lower memory consumption.
val timeframe = feed.timeframe
val logger = MemoryLogger(showProgress = false)
val jobs = ParallelJobs()
for (period in timeframe.split(2.years)) {
jobs.add {
// Create a new roboquant instance for each job
val roboquant = Roboquant(EMAStrategy(), AccountMetric(), logger = logger)
// Give the run a unique identifiable name
roboquant.runAsync(feed, period, name = "run-$period")
}
}
// Wait until all the jobs are finished
jobs.joinAllBlocking()
// If you are in Jupyter Notebook, you can easily plot a metric for all the runs
val equity = logger.getMetric("account.equity")
TimeSeriesChart(equity)
Pinpointing issues
It isn’t always easy to find out why a run isn’t behaving as expected. One way to pinpoint where the actual issue is located is to enable extra metrics recording:
val strategy = EMAStrategy()
val policy = FlexPolicy()
policy.enableMetrics = true
val roboquant = Roboquant(strategy, policy = policy, logger = ConsoleLogger())
roboquant.run(feed)
This will tell you for each step, how many actions
, signals
and orders
there were and enables you to draw some conclusions:
Metric | Observation | Possible problem |
---|---|---|
policy.actions |
always 0 |
The feed isn’t generating actions. For example, when using a LiveFeed outside trading hours. |
policy.signals |
always 0 |
The strategy isn’t generating signals. |
policy.orders |
always 0 |
The policy isn’t generating orders. For example, you don’t have enough buying power to buy a single BitCoin and didn’t enable fractional orders. |