Journal
Overview¶
A journal captures and/or logs information at each step of a run. It is optional and if you don’t provide one during a run, there is only the returned account at the end of the run to see what happened.
A journal should NOT modify any of the passed parameters.
API¶
The API of the Journal is a single track(...) method with all signals and orders generated during this step.
Below is a custom Journal that prints all the available info to the console at each step of the run.
from roboquant.journals import Journal
from roboquant.common import Event, Account, Signal, Order
class MyJournal(Journal):
def track(self, event: Event, account: Account, signals: list[Signal], orders: list[Order]) -> None:
print(f"event={event} account={account} singals={signals} orders={orders}")BasicJournal¶
The BasicJournal has low overhead and tracks the following information:
total number of events, and items
the total number of signals and orders
the maximum open positions
the total number of trades
the total number of unique assets
It will also log these values at each step in the run at info level.
MetricsJournal¶
MetricsJournal collects and records metrics throughout a run, making it easy to track performance indicators like P&L, Sharpe ratio, drawdown, and custom metrics.
import roboquant as rq
from roboquant.journals import MetricsJournal
from roboquant.util.metrics import PNLMetric, RunMetric
feed = rq.feeds.YahooFeed.us_stocks_10()
strategy = rq.strategies.EMACrossover(12, 25)
# Collect P&L, run metrics, and account-level metrics
journal = MetricsJournal(PNLMetric(), RunMetric())
account = rq.run(feed, strategy, journal=journal)
# Inspect recorded metrics as a time-series (DataFrame)
df = journal.get_metrics("pnl/equity")
print(df.tail()) pnl/equity
2026-09-24 04:00:00+00:00 3.865326e+06
2026-09-25 04:00:00+00:00 3.893693e+06
2026-09-28 04:00:00+00:00 3.888673e+06
2026-09-29 04:00:00+00:00 3.858897e+06
2026-09-30 04:00:00+00:00 3.855931e+06
You can also develop custom metrics by subclassing Metric and
implement the calc() method.
from roboquant.common.metric import Metric
class PositionCount(Metric):
"""Counts the number of open positions at each step."""
def calc(self, event, account, signals, orders) -> dict[str, float]:
return {
"positions": float(len(account.positions()))
}TensorBoardJournal¶
This journal is similar to the MetricsJournal, but rather than keeping the results of the metrics in memory it will write them to a TensorBoard compatible file.
So already during a run, the metrics can be inspected using a TensorBoard viewer.
from tensorboard.summary import Writer
import roboquant as rq
from roboquant.journals import TensorboardJournal
from roboquant.util.metrics import PNLMetric, RunMetric
feed = rq.feeds.YahooFeed.us_stocks_10()
# Compare runs with different parameters for the EMACrossover strategy
hyper_params = [(5, 10), (12, 25), (25, 50)]
for p1, p2 in hyper_params:
# Each run will be logged to a different directory
log_dir = f"runs/ema_{p1}_{p2}"
writer = Writer(log_dir)
journal = TensorboardJournal(writer, PNLMetric(), RunMetric())
strategy = rq.strategies.EMACrossover(p1, p2)
account = rq.run(feed, strategy, journal=journal)
writer.close()TrackerJournal¶
This journal tracks the created Signals, Orders at each step of a run.
So these are the new Signals generated by the Strategy, any new orders created by the Trader. This is regardless of the fact if Signals are converted into Orders, or if the Orders are already processed by the Broker.
After the run you can access these orders and signals either directly or through some of the included convenience methods.
Custom Journals¶
There are several reasons you might want to implement a custom Journal. For example, you want to be notified on a messaging platform if something (like a new order) happens during a live trading session.
import requests
WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN"
class DiscordJournal(Journal):
def track(self, event: Event, account: Account, signals: list[Signal], orders: list[Order]) -> None:
if orders:
msg = {"content": f"new orders {orders}"}
requests.post(WEBHOOK_URL, json=msg)Another use case is a journal that guards some condition and stops the run if the condition is met.
from roboquant import stop_run
class GuardJournal(Journal):
def track(self, event: Event, account: Account, signals: list[Signal], orders: list[Order]) -> None:
if account.cash_value() < 1_000:
stop_run("Not enough cash remaining")