val metric1 = AccountMetric()
val metric2 = ExposureMetric()
val metric3 = ProgressMetric()
val metric4 = VWAPMetric()
val metric5 = ReturnsMetric()
val metric6 = PNLMetric()
val metric7 = AlphaBetaMetric(sp500Asset, 250)
val metric8 = ScorecardMetric()
val roboquant = Roboquant(
strategy, metric1, metric2, metric3, metric4, metric5, metric6, metric7, metric8
)
Metrics & Loggers
Metrics allow you to monitor the progress of a run
and get the necessary insights on how a strategy is performing. Metrics are not limited to back testing and can also be used during forward testing, paper trading and live trading.
By default, the configured metrics are invoked at each step in a run. A single metric can return multiple values, each value having its own unique name. Metrics only calculate the values, the storing and/or logging of these values is done by a MetricsLogger
.
Standard Metrics
There are several commonly used metrics included with roboquant that can be added to your Roboquant
instance:
If a metric is very computationally intensive, and you don’t require it at each step, you can wrap it in a MetricsScheduler and configure when it should be invoked.
|
Custom Metrics
You can also develop your own metrics. All you have to do is implement the calculate
method from the Metric
interface and return MetricResults
, which is just a typealias
for Map<String, Double>
. Metrics also implement the Lifecycle
interface, so you can keep state in a metric if required and clean it when a lifecycle method is invoked.
If you don’t have any results at a certain step, you should return an emptyMap()
. In the calculate
function you have access to both the Account
and the Event
objects.
class MyMetric : Metric {
private val asset = Asset("AAPL")
override fun calculate(account: Account, event: Event): Map<String, Double> {
val metric1 = account.buyingPower.value
val metric2 = event.getPrice(asset) ?: Double.NaN
return mapOf("buyingpower" to metric1, "appleprice" to metric2)
}
}
Metrics Loggers
A metrics logger receives the metric values at each step and then stores and/or logs these values. All implement the MetricsLogger
interface.
The following metric loggers are available out of the box:
-
MemoryLogger
: this is the default logger used if no other logger is configured. It stores all the recorded values in memory and allows them to be queried afterwards. It also comes with an optional progress bar that shows how much time is left during a run.This logger is especially useful if you are using a Jupyter Notebook and want to directly plot some metrics after a run.
// You can always find out which metrics are available after a run val logger = roboquant.logger println(logger.getMetricNames()) // And plot a metric in a Jupyter Notebook val equity = logger.getMetric("account.equity") TimeSeriesChart(equity)
-
SilentLogger
— This logger just silently ignores all metric values and is mostly useful for unit tests -
InfoLogger
— Logs metrics using the built-in logger at INFO level -
ConsoleLogger
— Logs the metrics to the console using theprintln
statement -
QuestDBLogger
— Logs the metrics to a QuestDB database.// Without specifying a path, the logger will use // the default DB path ~/.roboquant/questdb-metrics/db val logger = QuestDBMetricsLogger() val rq = Roboquant(EMAStrategy(), AccountMetric(), logger = logger) // The logger can be used for multiple runs, even concurrently if required. // Just make sure every run has a unique name feed.timeframe.split(1.years).forEach { rq.run(feed, it , name = "run-${it.toPrettyString()}") }
You can access the data just like with the memory logger, or use a web console:
docker run --rm -p 9000:9000 -v "$HOME/.roboquant/questdb-metrics:/var/lib/questdb" questdb/questdb:7.3.1
Custom Loggers
You can also implement your own metrics logger, for example if you want to store values into a database or have a custom file format that you want to use. The following example shows a minimal implementation:
class MyDatabaseLogger : MetricsLogger {
private val database = Database()
override fun log(results: Map<String, Double>, time: Instant, run: String) {
for ((key, value) in results) database.store(key, value, time)
}
/**
* Optional, if you want to access the logged metrics via roboquant
*/
override fun getMetric(metricName: String, run: String): TimeSeries {
return database.retrieve(metricName, run)
}
/**
* Optional, if you want to access the logged metrics via roboquant
*/
override fun getMetricNames(run: String) = database.retrieveKeys(run)
}