Market LabDocs
Scripting V2 - PythonPlain Python Files

Artifacts

Save Python charts, reports, tables, and model output in a script job's artifact directory.

Python scripts often produce more than scalar metrics. Use ctx.artifact_path(name) to save charts, CSV files, model output, or reports in a directory owned by the current run.

def on_finish(ctx, history):
    path = ctx.artifact_path("performance.png")
    plt.savefig(path, dpi=160, bbox_inches="tight")
    plt.close()

    return {"meta": {"chart": path}}

The method returns an absolute path as a string and creates missing parent directories:

csv_path = ctx.artifact_path("tables/trades.csv")
frame.to_csv(csv_path, index=False)

Storage location

Artifacts are stored under:

~/.market-lab/artifacts/<job-id>/

Live runs use the script job ID. Backtests use a generated run-specific directory so one backtest does not overwrite another.

On Unix systems, Market Lab creates each job artifact directory with owner-only 0700 permissions.

Path safety

The name must remain inside the job directory. Absolute paths and parent traversal are rejected:

ctx.artifact_path("../outside.csv")  # rejected
ctx.artifact_path("/tmp/report.csv") # rejected

Use a relative name such as report.csv or charts/equity.png.

Plot runtime PnL

ctx.pnl() returns the PnL points recorded during the run. This makes the same runtime series available for post-analysis in on_finish:

def on_finish(ctx, history):
    import matplotlib.pyplot as plt
    from datetime import datetime, timezone
    import matplotlib.dates as mdates

    points = ctx.pnl()
    if not points:
        return

    times = [
        datetime.fromtimestamp(point["t"] / 1000, tz=timezone.utc)
        for point in points
    ]
    pnl = [point["pnl"] for point in points]

    fig, ax = plt.subplots(figsize=(10, 5))
    ax.plot(times, pnl, linewidth=1.5, label="Net PnL")
    ax.axhline(0, color="gray", linewidth=1, linestyle="--")

    locator = mdates.AutoDateLocator()
    ax.xaxis.set_major_locator(locator)
    ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(locator))

    path = ctx.artifact_path("pnl.png")
    fig.savefig(path, dpi=160)
    print(f"Saved chart to: {path}")
    plt.close(fig)

Use ctx.pnl(0) for the latest point or ctx.pnl(1) for the previous point. An unavailable index returns None.

Complete pandas and matplotlib example

import matplotlib.pyplot as plt
import pandas as pd

SOURCE = "btc@candles@binancef:timeframe=60"


def on_finish(ctx, history):
    frame = pd.DataFrame(history.source(SOURCE))
    if frame.empty:
        return

    frame["sma_20"] = frame["c"].rolling(20).mean()

    csv_path = ctx.artifact_path("tables/candles.csv")
    frame.to_csv(csv_path, index=False)

    axis = frame.plot(x="t", y=["c", "sma_20"], title="BTC close and SMA")
    axis.figure.savefig(
        ctx.artifact_path("charts/sma.png"),
        dpi=160,
        bbox_inches="tight",
    )
    plt.close(axis.figure)

    return {
        "meta": {
            "candles": csv_path,
            "chart": ctx.artifact_path("charts/sma.png"),
        }
    }

on_finish has a longer runtime limit than event hooks so final reporting can do more work. It still must complete within 60 seconds.

Retention responsibility

Market Lab does not automatically delete artifact directories and does not impose an artifact disk quota. Monitor ~/.market-lab/artifacts and remove old run directories according to your own retention policy.

ctx.artifact_path is a safe default, not a filesystem sandbox. Trusted Python code can still write elsewhere using normal Python APIs and the operating-system permissions of the daemon user.

On this page