Back to Blog · Software Architecture

Tracing Every Trade Back to a Git Tag: A Strategy Lifecycle Pipeline

A Laravel API that receives deployment events from a shell script, links every trade to a git tag, and surfaces the full lifecycle of a trading strategy across backtest, paper, and live environments.

MF
Martin Fournier
· June 02, 2026 · 5 MIN READ
Illustration for: Tracing Every Trade Back to a Git Tag: A Strategy Lifecycle Pipeline

After building a backtest engine that produces reliable signals and a broker connector that executes them, the next question is: how do you know a live trade relates to the backtest that produced it?

Without a traceable link between a trade and the code that generated it, every P&L number is just a number. You cannot answer whether a losing trade was caused by a bug in the strategy, a slippage issue, or a market regime shift. You cannot confidently promote a strategy from backtest to paper, then to live, because you have no chain of custody.

The solution is a strategy lifecycle pipeline: a Laravel API that sits between your deployment script and your trade database, recording every promotion, every trade, and every performance metric against a specific git tag.

What traceability looks like

The pipeline records four layers of provenance for every trade:

  1. Git tag - the exact commit that produced the strategy binary
  2. Deployment phase - backtest, paper, or live
  3. Environment metrics - Sharpe, drawdown, win rate at the time of promotion
  4. Per-trade metadata - slippage, fill price, execution timestamp

When you look at a live trade six months later, you can trace it back to the git tag, inspect the code that generated the signal, and compare the live fill against the backtest fill for the same bar.

The deployment table

The central table in this pipeline is strategy_deployments. Each row represents one promotion of a strategy through a phase of its lifecycle:

Schema::create('strategy_deployments', function (Blueprint $table) {
    $table->id();
    $table->string('strategy_name');
    $table->string('phase'); // backtest | paper | live
    $table->string('status'); // pending | running | stopped
    $table->string('git_tag')->nullable();
    $table->decimal('pnl', 16, 4)->nullable();
    $table->decimal('win_rate', 5, 2)->nullable();
    $table->decimal('sharpe', 8, 4)->nullable();
    $table->decimal('max_drawdown', 8, 4)->nullable();
    $table->timestamps();
});

Every deployment is tied to a git tag. When the strategy code changes, a new tag is created, and a new deployment row captures the promotion. No silent mutations.

The deploy.sh hook

The deployment shell script already exists to compile the Java Maven project and push the binary. Adding the API call took fifteen lines:

# Called after successful compilation and tagging
curl -X POST https://trading.example.com/api/deployments \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "strategy_name": "'"$STRATEGY"'",
    "phase": "'"$PHASE"'",
    "git_tag": "'"$GIT_TAG"'",
    "status": "running"
  }'

The script calls this endpoint at three points: when a strategy graduates from backtest to paper, from paper to live, and when a strategy is stopped. No manual steps. No forgotten entries.

Linking trades to deployments

The trades table gains a single foreign key: strategy_deployment_id. A trade belongs to exactly one deployment. This replaces the old approach of storing a strategy name string on each trade, which broke when two versions of the same strategy ran concurrently.

Schema::table('trades', function (Blueprint $table) {
    $table->foreignId('strategy_deployment_id')
        ->nullable()
        ->constrained()
        ->nullOnDelete();
    $table->decimal('slippage', 10, 5)->nullable();
});

Slippage is captured per trade. This is the key metric that tells you whether a backtest assumption is holding in live markets.

The timeline endpoint

The API exposes a timeline view that aggregates everything for a given strategy:

GET /api/strategies/{name}/timeline

It returns each deployment in order, with the trades that belong to it, the performance metrics recorded at each phase, and the git tag. This is the single source of truth for answering questions like:

  • Did the Sharpe degrade when we moved from paper to live?
  • Was drawdown worse on this git tag than the previous one?
  • Is slippage consistently above the backtest assumption?

Deployment metrics as events

Metrics are not computed on the fly. When a deployment finishes or at daily intervals, the pipeline records a snapshot:

POST /api/deployments/{id}/metrics
{
    "pnl": 1245.30,
    "win_rate": 62.5,
    "sharpe": 1.84,
    "max_drawdown": 8.2,
    "total_trades": 198
}

This is important: the metric values are frozen at the time of recording. Later recomputation will not change historical numbers. The dashboard shows what was known at each point in time, not a retrospective revision.

What changed

Before the pipeline, a trade had a strategy name string, no deployment context, and no link to source control. The dashboard showed aggregate numbers that mixed trades from different code versions.

After the pipeline, every trade carries a deployment ID, every deployment carries a git tag, and the dashboard can filter by code version. The backtest engine and the live broker now share the same traceability layer.

This is not a novel pattern. It is standard practice in regulated trading firms and has been documented in the industry for years. The difference is that setting it up in a Laravel monolith requires no trading-specific infrastructure. A few migrations, a controller, a shell script hook, and you have full lifecycle traceability.

The bottom line

Traceability is infrastructure, not features. Building it into the deployment pipeline from the start means every trade is accountable to a specific version of strategy code. When P&L moves, you know which code moved it. That alone eliminates an entire class of debugging guesswork.