Writing a logger¶
A logger receives the logs of a simulation: the orders, cancels and executions, and the beginning and end of the
simulation, of each session and of each market step. The built-in MarketStepPrintLogger and
MarketStepSaver only record the market prices at each step (see Running a simulation).
This tutorial writes a logger that prints a summary of each session and saves every trade to a CSV file.
The complete script is tutorial_custom_logger.py.
The logger¶
A logger inherits from Logger and overrides the methods of the logs it needs. The others do
nothing:
Method |
Log |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The order, cancel, expiration and execution logs give the IDs of the market and of the agents. The other logs
give the simulator (log.simulator).
class TradeLogger(Logger):
"""Print a summary of each session and save every execution to a CSV file."""
def __init__(self, path: str) -> None:
"""Initialize the logger.
Args:
path (str): path of the CSV file written at the end of the simulation.
"""
super().__init__()
self.path: str = path
self.executions: List[ExecutionLog] = []
self.n_orders: int = 0
self.volume: int = 0
def process_session_begin_log(self, log: SessionBeginLog) -> None:
"""Reset the counters at the beginning of each session.
Args:
log (SessionBeginLog): the log of the session that begins.
"""
self.n_orders = 0
self.volume = 0
def process_order_log(self, log: OrderLog) -> None:
"""Count the orders accepted by the markets.
Args:
log (OrderLog): the accepted order.
"""
self.n_orders += 1
def process_execution_log(self, log: ExecutionLog) -> None:
"""Keep the execution and add its volume to the volume of the session.
Args:
log (ExecutionLog): the execution.
"""
self.executions.append(log)
self.volume += log.volume
def process_session_end_log(self, log: SessionEndLog) -> None:
"""Print the counters at the end of each session.
Args:
log (SessionEndLog): the log of the session that ends.
"""
print(
f"{log.session.name}: {self.n_orders} orders, "
f"{self.volume} shares traded"
)
def process_simulation_end_log(self, log: SimulationEndLog) -> None:
"""Write all the executions to the CSV file.
Args:
log (SimulationEndLog): the log of the end of the simulation.
"""
with open(self.path, "w", newline="", encoding="utf-8") as fp:
writer = csv.writer(fp)
writer.writerow(["time", "market", "price", "volume", "buyer", "seller"])
for execution in self.executions:
writer.writerow(
[
execution.time,
self.simulator.id2market[execution.market_id].name,
execution.price,
execution.volume,
self.simulator.id2agent[execution.buy_agent_id].name,
self.simulator.id2agent[execution.sell_agent_id].name,
]
)
__init__must callsuper().__init__().The runner sets
self.simulatorwhen the runner is created, before the simulation starts. The logger uses it to find the names of the market and of the agents from their IDs.An
ExecutionLoggives the step (time), the market, the buyer and the seller, their order IDs, the price and the volume of one execution.The logger keeps the executions in memory and writes the file at the end of the simulation. This also keeps it usable with the process runner, which cannot copy a logger that holds an open file (see Parallel runners).
When the logs are processed¶
The market step logs are processed at once, during the step. So, in process_market_step_end_log, the market
is at the end of that step, which is how MarketStepSaver reads its prices.
The order, cancel, expiration and execution logs are kept by the logger until it receives the next session or
simulation log, at the beginning or at the end of a session or of the simulation. They are then processed in
the order they happened, before that log. In process_execution_log, the market and the agents are therefore
already at the end of the session: use the fields of the log, not the current state of the market or the
agents.
In this logger, the orders and executions of a session are counted when the session ends, just before
process_session_end_log prints them.
Using the logger¶
A runner takes one logger:
def run_simulation(seed: int, path: str) -> SequentialRunner:
"""Run the simulation with the trade logger.
Args:
seed (int): seed of the random number generator.
path (str): path of the CSV file to write.
Returns:
SequentialRunner: the runner after the run.
"""
runner = SequentialRunner(
settings=CONFIG, prng=random.Random(seed), logger=TradeLogger(path=path)
)
runner.main()
return runner
To record several things, handle them all in one logger. run_simulation(seed=42, path="trades.csv") prints
the summaries of the two sessions, then the two time lines of main():
warmup: 100 orders, 0 shares traded
main: 500 orders, 205 shares traded
# INITIALIZATION TIME 0.0019999
# EXECUTION TIME 0.0909915
In each step, one FCN agent places one order (maxNormalOrders is 1), and orders are only executed in the
main session. trades.csv starts with:
time,market,price,volume,buyer,seller
100,Market,298.11165,1,FCNAgents-16,FCNAgents-66
100,Market,298.11165,1,FCNAgents-91,FCNAgents-66
100,Market,298.11165,1,FCNAgents-87,FCNAgents-55
At step 100, the first step of main, the orders placed during warmup start to be executed.