Skip to content

Parameter surfaces and animations

ASRQuant 0.3.0 treats a surface as a view of a finite parameter experiment. A surface has two displayed numeric axes. Additional parameters can be fixed or converted into animation frames.

Core API

surface = lab.parameter_surface(
    experiment,
    parameter_grid={
        "gamma": [0.5, 1.0, 2.0, 4.0],
        "cost_bps": [0, 2, 5, 10],
        "hedge_every": [1, 5, 20],
        "volatility": [0.15, 0.30],
        "model": ["linear", "neural"],
    },
    x="gamma",
    y="cost_bps",
    animate_by=["hedge_every", "volatility", "model"],
    metric="metrics.utility",
    z_name="utility",
)

The callable is invoked once for every point in the finite grid. It may return:

  • a scalar;
  • a mapping or pandas Series;
  • a dataclass or ordinary object;
  • an ASRQuant result object;
  • any result reducible by metric=lambda result: ....

Dimensional interpretation

For a parameter grid with dimensions

(gamma, cost_bps, hedge_every, volatility, model)

and x="gamma", y="cost_bps", the surface geometry is two-dimensional. hedge_every, volatility, and model form the animation frame table. The number of frames is the Cartesian product of their candidate values.

This means ASRQuant can explore an arbitrary number of finite parameters, but it does not claim to draw a literal geometric surface with more than two axes. Higher dimensions are represented through slices and animation.

Metric selection

metric=None                         # callable returns one scalar
metric="Sharpe"                    # mapping or Series key
metric="metrics.Sharpe"            # dotted object path
metric=lambda result: result.cvar  # custom reduction

Fixed parameters

A parameter that is neither an axis nor animated must have one candidate value or be supplied in fixed_params.

surface = lab.parameter_surface(
    option_experiment,
    {"strike": strikes, "maturity": maturities},
    x="strike",
    y="maturity",
    fixed_params={"spot": 100, "rate": 0.03, "volatility": 0.20},
)

Vectorized functions

When the experiment accepts NumPy mesh arrays for the two displayed axes, set vectorized=True. Extra frame parameters remain scalar in each frame.

surface = lab.parameter_surface(
    lambda x, y, theta: np.sin(x + theta) * np.cos(y),
    {"x": x, "y": y, "theta": theta},
    vectorized=True,
)

Parallel evaluation and failures

surface = lab.parameter_surface(
    expensive_experiment,
    grid,
    n_jobs=8,
    error_policy="nan",
    max_evaluations=200_000,
)

n_jobs uses threads and is most useful for I/O-bound or native-code-backed experiments. Stateful callables must be thread-safe. error_policy="nan" keeps the remaining grid and records failures in surface.metadata["errors"].

Backtest parameter exploration

surface = lab.backtest_parameter_surface(
    "sma",
    {
        "fast": [5, 10, 20],
        "slow": [40, 80, 120],
        "costs_bps": [0, 5, 10],
        "execution_delay": [0, 1, 2],
    },
    x="fast",
    y="slow",
    animate_by=["costs_bps", "execution_delay"],
    metric="Sharpe",
)

costs_bps and execution_delay are interpreted as backtest-contract parameters. Other names are passed to the strategy.

Existing CSV or DataFrame results

results = pd.read_csv("deep_hedging_results.csv")
surface = lab.surface_from_frame(
    results,
    x="risk_aversion",
    y="cost_bps",
    z="oos_cvar",
    frame_cols=["hedge_every", "volatility", "seed"],
)

Plotting and export

surface.plot("surface", frame=0)
surface.plot("heatmap", frame=0)
surface.plot("contour", frame=0)

surface.save_animation("experiment.html")       # interactive Plotly slider
surface.save_animation("experiment.gif")        # Pillow writer
surface.save_animation("experiment.mp4")        # FFmpeg required
surface.export_frames("frames", kind="surface")

HTML is the recommended portable format because it preserves camera controls, play/pause controls, and the complete parameter label for every frame.

Research safeguards

  • A visually smooth surface is not evidence of statistical validity.
  • Parameter grids should be specified before observing out-of-sample results.
  • Multiple testing and selection bias remain present when many configurations are evaluated.
  • Expensive stochastic experiments should use explicit random seeds or common random numbers when comparisons require low Monte Carlo noise.
  • A large Cartesian product can be computationally expensive; use max_evaluations, coarse-to-fine grids, or fixed slices.