Skip to the page
GUIDES/RUN A SCENARIO

Run a scenario

By the end of this page you will have scored an agent with and without a packaged market crisis on the same seeds, and written a scenario of your own with a fingerprint you can cite. Every target, operation and shape is in the reference, Scenarios. The code blocks on this page are one Python session and run in order.

Load a scenario and read it

A scenario keeps what happened to the market (its shocks) apart from what its author assumes that caused (its transmission). describe() prints the two under separate headings, so read it before you run anything. tf.Scenario.available() names every scenario that ships.

import tradefloor as tf

crisis = tf.Scenario.load("liquidity_crisis")
print(crisis.describe())
SCENARIO  liquidity_crisis

A funding event with an earnings shock: quoted depth collapses to 40 per
cent and the VIX goes three-and-a-half-fold for twenty-five days, credit
widens 50 basis points, and earnings fall 15 per cent over two months
and recover over the next four. Volatility is then released back to the
endogenous chain, and depth is written back, because nothing in the
engine writes that column. Example experimental assumptions, not a
forecast.

Schema      v1
Fingerprint sha256:7d8c8cc86e74bf02bcf800b3733a774b18fcada9b44ec47d69e84014489f8461
Source      liquidity_crisis.yml

Exogenous shocks
----------------------------------------------------------
  day 50-74          market.liquidity         x0.4
  day 50-74          macro.vix                x3.5
  day 50..91 ramp    market.earnings          x0.85
  day 92..175 ramp   market.earnings          x1.17647

Assumed transmission
----------------------------------------------------------
  day 50-74          macro.corporate_yield    +0.50pp

The transmission entries are ASSUMPTIONS made by whoever wrote this
scenario. Nothing here derives them, and nothing here is a forecast of
how a real market would respond.

Days count from the day the scenario is first applied, which for tf.evaluate is the run's first day. This crisis starts on day 50, so a run has to be longer than 50 days to see it.

Score an agent with and without it

Run the same agent on each seed with scenario= and without it. A seed is the same market in both runs up to day 50, so the difference in P&L is what the scenario did to this agent.

market = tf.Universe.random(10, seed=101)

def score(seed, scenario=None):
    # buy-and-hold's P&L over 60 days, with the scenario or without it
    agents = {"buy_and_hold": tf.reference_agents(seed=3)["buy_and_hold"]}
    card = tf.evaluate(agents, seed=seed, universe=market, days=60,
                       scenario=scenario)["buy_and_hold"]
    return card.pnl

gaps = []
for seed in (1, 2, 3):
    calm, hit = score(seed), score(seed, crisis)
    gaps.append(hit - calm)
    print(f"seed {seed}: {calm:+10,.0f} without  {hit:+10,.0f} with  "
          f"{hit - calm:+10,.0f} difference")
print(f"mean difference {sum(gaps) / len(gaps):+,.0f}")
seed 1:    -36,158 without    -182,859 with    -146,701 difference
seed 2:    -18,561 without    -133,920 with    -115,359 difference
seed 3:    +29,085 without     -17,492 with     -46,577 difference
mean difference -102,879

Buy-and-hold trades on its first step only, so the thinner book costs it nothing, and the difference is what prices did in the first ten days of the crisis. Three seeds give a direction and not a size to quote. For several agents across many seeds, pass the same scenario= to tf.rank, as in Compare strategies.

Write your own

A scenario built in Python takes shock for an event from outside the market and assume for an effect you are assuming it has. This one halves quoted depth for twenty days and assumes credit widens 150 basis points over the same days.

squeeze = (tf.Scenario(name="credit_squeeze")
           .shock("market.liquidity", operation="multiply", value=0.5,
                  at=10, duration=20)
           .assume("macro.corporate_yield", operation="add", value=0.015,
                   at=10, duration=20))
print(squeeze.fingerprint)
sha256:1022f79ec4d397f352f46ada50d3abfddd500b863a24b536fa3f7c8c01d7c848

The fingerprint is a sha256 of the resolved scenario, and a manifest records it with every run that used the scenario. The same scenario written in YAML resolves to the same document, so it has the same fingerprint.

text = """
version: 1
scenario:
  name: credit_squeeze
  shocks:
    - target: market.liquidity
      operation: multiply
      value: 0.5
      at: 10
      duration: 20
  transmission:
    - target: macro.corporate_yield
      operation: add
      value: 0.015
      at: 10
      duration: 20
"""
print(tf.Scenario.from_yaml(text).fingerprint == squeeze.fingerprint)
True

Changing a value, a day or the order of two interventions changes the fingerprint. Comments, key order and formatting in the YAML do not. Pass squeeze as scenario= to run it, as with a shipped one.

Next steps

  • The targets lists every target a scenario can move, and Writing one the operations, shapes and YAML form.
  • The command line lists, validates, shows and compares scenario files without running a market.
  • Fork a market applies a scenario to one arm of a fork, so both arms share the past before it.