NexusTrade guide

Build and backtest an algorithmic call diagonal spread

A bullish rule buys a later ATM call and sells a nearer higher-strike call. Different expirations change the payoff and early-assignment exposure.

As of 2026-10-10

Follow the example

Choose a step to follow the example. Opening a request lets you review it; it does not submit a job or change your account.

Step 1 of 3

Read the signal and every leg

Read the entry condition, requested allocation and each leg’s strike and expiration selectors in the saved rules below.

Entry
Saved candidate: Price(SPY) > SMA(SPY,50,Day) AND zero matching option positions
Position sizing
Saved candidate: One strategy unit; leg ratios apply. Buying power, collateral and liquidity can restrict orders.
Signal exit
Saved candidate: Price(SPY) <= SMA(SPY,50,Day); close whole matching spread at 14 or fewer DTE
Starting capital
Saved candidate: $10000.00
What you should see

The plain-language purpose matches the saved rules below. Keep the rule, purchase size and exit together when creating your version.

Call diagonal spread: completed 2024 backtest12.99K9.9K2024-01-022024-12-31
Call diagonal spreadFull-capital SPY buy and holdHistorical simulation
Completed 2024 historical simulation. The plotted observations and comparison series are preserved.
Step 2 of 3

Completed historical backtest

Inspect the recorded contract-selection rejection below, then compare its capital requirement with the requested allocation.

What you should see

The retained resolution event shows rejection and zero resolved quantity. A completed backtest can contain no fills.

Preview the same rules in Aurora: authored process map
Authored task map. It describes what to inspect; it is not an account screenshot or a measured result.
Step 3 of 3

Preview the same rules in Aurora

Open the reviewed request, check its selectors and exits, and keep automated trading off. Review any test cost separately before submission.

What you should see

An inspectable draft of this example. Historical synthetic pricing is visible, not a promise of available live quotes or identical execution.

The algorithm and its purpose

The rules below trade SPY options. The completed 2024 backtest includes the portfolio curve, contract-resolution records and any recorded fills.

Scroll sideways to compare all columns. Select a heading to sort.

EntryPrice(SPY) > SMA(SPY,50,Day) AND zero matching option positions
Position sizingOne strategy unit; leg ratios apply. Buying power, collateral and liquidity can restrict orders.
Signal exitPrice(SPY) <= SMA(SPY,50,Day); close whole matching spread at 14 or fewer DTE
Starting capital$10000.00
Spread typediagonal
ExecutionMarket; $0.65 commission per option contract, default 0.5 half-spread fraction plus applicable liquidity impact.

Contract selectors and ratios

Strike distances are percentages of the underlying price, not fixed market strikes or delta targets. Positive distance means out of the money: a higher strike for calls and a lower strike for puts. Negative distance means in the money; zero targets at the money (ATM). Days to expiration (DTE) measures the time remaining until a contract expires. Nearest expiration must lie within each leg's DTE range. Inspect the resolved contracts rather than assuming the authored offsets produce exact strike widths.

These legs intentionally use different expirations. A single-expiration payoff diagram does not describe the whole position. Inspect each selected expiration, the short-leg lifecycle and reserved collateral.

Scroll sideways to compare all columns. Select a heading to sort.

1longcall0%160 to 90 DTE
2shortcall5%130 to 45 DTE

Create the exact inactive draft

Ask Aurora to build the rules above, review the preview, then save the draft while keeping automated trading off. A chat draft is inspected in Portfolio > Drafts or Aurora; the native Strategies editor belongs to a paper or live portfolio.

In an inactive native portfolio, use Open Options Position, choose the matching structure preset and verify each leg above. Under Allocation, set Type Contracts and Amount 1. Add the entry conditions, including zero matching option positions. Add both Close Options Position rules separately, matching SPY and the spread type with close scope spread and quantity all. A preset adds contract legs; it does not supply the complete entry or exit algorithm.

Plan a separate variant and later test

Review the optional next experiment

After reviewing the recorded example, use the prepared request above for the original rules. This optional plan covers a separate variant and a later test.

  1. Create one variant, then test unseen dates

    Ask: "Change only the timed close trigger from 14 DTE to 10 DTE. Show the rule diff and compare both candidates on the 2024 development period with every other setting fixed." After choosing, freeze the rules and select another test period you have not inspected. The displayed 2024 results already make 2024 a viewed development period. Keep any later revisions in a new research cycle.

Create and backtest with Python

Use the Python SDK instead of Aurora

Python SDK 1.42.0. Save the runner below as run_call_diagonal_spread.py.

Install and download

Shell
python3 -m pip install nexustrade==1.42.0
export NEXUSTRADE_API_KEY="YOUR_API_KEY"
curl --fail --output call_diagonal_spread.py https://nexustrade.io/seo/strategy-examples/call-diagonal-spread.py

Create, test and inspect

Python
from call_diagonal_spread import create_call_diagonal_spread, backtest_call_diagonal_spread

book = create_call_diagonal_spread()
completed = backtest_call_diagonal_spread()
print(completed["result"])

Run

Shell
python3 run_call_diagonal_spread.py

Python strategy and backtest functions

View complete Python SDK functions

The backtest function submits the 2024 test with SPY as its benchmark, records events and waits for the result. Use a new request key when changing rules, dates or fees.

Python
import nexustrade as nt


def create_call_diagonal_spread():
    asset = nt.stock_asset("SPY")
    return nt.portfolio(
        "Public SEO example - Call diagonal spread",
        [
            nt.strategy(
                "Open Call diagonal spread",
                ((nt.Price(asset) > nt.SMA(asset, 50, "Day")) & (nt.OptionPositionCount("SPY", "call", "long", "diagonal") == nt.Value(0))),
                nt.open_option(
                    builder=nt.options_builder(
                        legs=[
                            nt.leg(
                                option_type="call",
                                direction="long",
                                min_days_to_expiration=60,
                                max_days_to_expiration=90,
                                preference="nearest",
                                distance=0,
                                ratio=1
                            ),
                            nt.leg(
                                option_type="call",
                                direction="short",
                                min_days_to_expiration=30,
                                max_days_to_expiration=45,
                                preference="nearest",
                                distance=5,
                                ratio=1
                            )
                        ],
                        underlying_symbol="SPY",
                        spread_type="diagonal"
                    ),
                    allocation={'type': 'contracts', 'amount': 1}
                )
            ),
            nt.strategy(
                "Close when signal invalidates",
                (nt.Price(asset) <= nt.SMA(asset, 50, "Day")),
                nt.close_option(
                    underlyings=['SPY'],
                    spread_type="diagonal",
                    close_scope="spread",
                    quantity={'type': 'all'}
                )
            ),
            nt.strategy(
                "Close at 14 DTE",
                (nt.Value(1) > nt.Value(0)),
                nt.close_option(
                    underlyings=['SPY'],
                    spread_type="diagonal",
                    close_scope="spread",
                    quantity={'type': 'all'},
                    triggers=[nt.dte_trigger(max_dte=14)]
                )
            ),
        ],
        initial_value=10000,
        supports_fractional_shares=False,
        alerts_enabled=False,
        dividend_policy='cash',
    )


def backtest_call_diagonal_spread(client=None):
    client = client or nt.NexusTradeClient.from_environment()
    operation = client.create_backtest(
        nt.backtest(
            create_call_diagonal_spread(),
            start_date="2024-01-01", end_date="2024-12-31",
            interval="Day", baseline_symbol="SPY",
            initial_value=10000, generate_events=True,
            dividend_policy="cash",
            fee_config={"Stock": {"type": "percent", "amount": 0.1},
                        "Cryptocurrency": {"type": "percent", "amount": 0.7},
                        "Option": {"type": "dollars", "amount": 0.65}},
        ),
        idempotency_key="explore-call-diagonal-spread-2024-spy-v1",
    )
    return client.wait_for_backtest(operation["id"])


# Build locally: book = create_call_diagonal_spread()
# Run and inspect: completed = backtest_call_diagonal_spread(); print(completed["result"])

Create and backtest with TypeScript

Use the TypeScript SDK instead of Aurora

TypeScript SDK 1.42.0. Save the runner below as run-call-diagonal-spread.ts.

Install and download

Shell
npm install nexustrade@1.42.0
npm install --save-dev tsx
export NEXUSTRADE_API_KEY="YOUR_API_KEY"
curl --fail --output call-diagonal-spread.ts https://nexustrade.io/seo/strategy-examples/call-diagonal-spread.ts

Create, test and inspect

TypeScript
import { createCallDiagonalSpread, backtestCallDiagonalSpread } from "./call-diagonal-spread";

async function main() {
  const book = createCallDiagonalSpread();
  const completed = await backtestCallDiagonalSpread();
  console.log(completed.result);
}

main().catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});

Run

Shell
npx tsx run-call-diagonal-spread.ts

TypeScript strategy and backtest functions

View complete TypeScript SDK functions

The same rules, dates and fees are used in both SDKs. The backtest function keeps its request key across retries.

TypeScript
import * as nt from "nexustrade";

export function createCallDiagonalSpread() {
  const asset = nt.stockAsset("SPY");
  return nt.portfolio(
    "Public SEO example - Call diagonal spread",
    [
      nt.strategy(
          "Open Call diagonal spread",
          nt.and(
              nt.gt(nt.Price(asset), nt.SMA(asset, 50, "Day")),
              nt.eq(nt.OptionPositionCount("SPY", "call", "long", "diagonal"), nt.Value(0))
          ),
          nt.openOption({ builder: nt.optionsBuilder({ legs: [nt.leg({ optionType: "call", direction: "long", minDaysToExpiration: 60, maxDaysToExpiration: 90, preference: "nearest", distance: 0, ratio: 1 }), nt.leg({ optionType: "call", direction: "short", minDaysToExpiration: 30, maxDaysToExpiration: 45, preference: "nearest", distance: 5, ratio: 1 })], underlyingSymbol: "SPY", spreadType: "diagonal" }), allocation: {"type": "contracts", "amount": 1} })
      ),
      nt.strategy(
          "Close when signal invalidates",
          nt.lte(nt.Price(asset), nt.SMA(asset, 50, "Day")),
          nt.closeOption({ underlyings: ["SPY"], spreadType: "diagonal", closeScope: "spread", quantity: {"type": "all"} })
      ),
      nt.strategy(
          "Close at 14 DTE",
          nt.gt(nt.Value(1), nt.Value(0)),
          nt.closeOption({ underlyings: ["SPY"], spreadType: "diagonal", closeScope: "spread", quantity: {"type": "all"}, triggers: [nt.dteTrigger({ maxDte: 14 })] })
      ),
    ],
    { initialValue: 10000, supportsFractionalShares: false, alertsEnabled: false, dividendPolicy: "cash" }
  );
}

export async function backtestCallDiagonalSpread(client = new nt.NexusTradeClient()) {
  const operation = await client.createBacktest(
    nt.backtest(createCallDiagonalSpread(), {
      startDate: "2024-01-01", endDate: "2024-12-31",
      interval: "Day", baselineSymbol: "SPY",
      initialValue: 10000, generateEvents: true,
      dividendPolicy: "cash",
      feeConfig: {
        Stock: { type: "percent", amount: 0.1 },
        Cryptocurrency: { type: "percent", amount: 0.7 },
        Option: { type: "dollars", amount: 0.65 },
      },
    }),
    { idempotencyKey: "explore-call-diagonal-spread-2024-spy-v1" }
  );
  if (typeof operation.id !== "string") throw new Error("Missing backtest ID");
  return client.waitForBacktest(operation.id);
}

// Build locally: const book = createCallDiagonalSpread();
// Run and inspect: const completed = await backtestCallDiagonalSpread(); console.log(completed.result);

Completed historical backtest

This exact saved candidate completed its fixed 2024 Day simulation on 2026-10-10. Starting capital was $10000.00. The request enabled detailed events and cash dividends. The curve below contains all 519 exported history points.

The SPY comparison invests the full starting balance, reinvests dividends and applies no strategy commission. This strategy requests one spread unit with its declared fees. Use the cash, position value and collateral history to explore how each deploys capital.

Scroll sideways to compare all columns. Select a heading to sort.

StatusCOMPLETE
Requested period2024-01-01 through 2024-12-31
Starting capital$10000.00
Modeled return0.00%
Reported maximum drawdown0.00%
Full-capital SPY comparator return25.30%
Recorded commissions$0.00
Engine closed-trade count0
Peak reserved collateral$0.00
Median reserved collateral while holding positionsNot applicable: no positions held
Pricing contract version8
Observed / synthetic half-spread calls0 / 0
Unknown-timing quote accesses79378
Missing-current-quote checks0
Rejected fill groups / marks0 / 0
Stale basket / single-option marks0 / 0
Call diagonal spread: completed 2024 backtest12.99K9.9K2024-01-022024-12-31
Call diagonal spreadFull-capital SPY buy and holdHistorical simulation

Why this run has no executed round trip

The completed run placed no trades. The retained 2024-12-26 contract-selection attempt was rejected: Cannot afford even 1 contract: resolved quantity is 0 (max loss ≈ $118919.01 per spread unit vs effective allocation). Increase allocation, use contract-based sizing, or choose a structure with lower capital per contract.

Inspect the requested one-unit allocation and $10,000 buying power beside that recorded capital requirement before designing another test. The zero-return result describes uninvested cash.

JSON
{
  "time": "2024-12-26T21:00:00.000Z",
  "status": "rejected",
  "reason": "Cannot afford even 1 contract: resolved quantity is 0 (max loss ≈ $118919.01 per spread unit vs effective allocation). Increase allocation, use contract-based sizing, or choose a structure with lower capital per contract.",
  "riskAudit": {
    "rulesetVersion": 1,
    "outcome": "rejected",
    "ruleCodes": [
      "RESOLVER_REJECT"
    ],
    "longSharesAvailable": 0,
    "unhedgedShortCallContractsPerSpread": 0,
    "buyingPowerEffective": 10000,
    "allocationRequested": 1,
    "allocation": {
      "type": "contracts",
      "amount": 1
    },
    "portfolioValue": 10000,
    "underlyingPrice": 601.3400268554688,
    "committedCostBefore": 0
  }
}

Explain this options decision with Aurora

Ask Aurora to connect the recorded signal, contract choice, allocation and fills. It receives this page's captured decision and order fields, so you can follow its explanation back to each value.

Download the result and explore the events

Download the exact strategy inputs, completed result, full history, pricing counters and captured events. You can use these records to follow the strategy from its signal to the recorded contract-selection rejection. Detailed events remain available in the product for 3 days; this saved example stays readable.

Explore this strategy further

Inspect matched quantities, actual strike widths, expirations and all close orders. For sold options, review collateral and exercise or assignment exposure separately from premium income. A condition firing can emit zero close orders; look at its evaluation and the Filled-order records.

Try another date range or change one rule, then compare the new backtest with this saved 2024 result. Inspect which trades and periods explain the difference.

Continue exploring

Discussion

Sign in or create a free account to join the discussion.

No comments yet.

Aurora · AI research assistant

Try this in Aurora

Edit the request, then open it in Aurora. You choose when to send it.