Strategy Development
Strategies are C# classes in HyperionX.Custom.Strategies. They derive from Strategy and can evaluate signals, submit orders, manage positions, and report performance.
New to Code Lab strategies? Complete Build Your First Strategy first. It covers the Code Lab template, a complete source file, compilation, chart loading, LocalPaper, and a reusable AI prompt. Use this page for the design model and the Strategy And Order Reference for exact overloads.
A strategy can place real orders when enabled against a connected account. Build the logic, backtest it, then test playback or simulation with an explicitly selected LocalPaper account before selecting live routing.
HyperionX Strategy Contract
When adapting generated code, keep these non-negotiable boundaries:
- Namespace:
HyperionX.Custom.Strategies. - Base class:
Strategy. - Lifecycle:
OnStateChanged()andOnBarUpdate(). - Normal entries:
EnterLong...andEnterShort.... - Normal exits:
ExitLong...andExitShort.... - Managed protection:
SetStopLoss...andSetProfitTarget.... - Order state:
OnOrderUpdate(Order)andOnExecutionUpdate(Order).
Methods such as Buy(...), Sell(...), ShortSell(...), Cover(...), OnStateChange(), and OnData(...) are not the established HyperionX Code Lab strategy contract. Do not accept an AI-generated method merely because it resembles another trading platform.
Minimal managed strategy
This example uses the preferred managed order helpers. It sets the stop and target before submitting each entry so protection is associated with the matching entry signal.
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using HyperionX.Core.Attributes;
using HyperionX.Core.Enums;
using HyperionX.Custom.Strategies;
namespace HyperionX.Custom.Strategies;
public class MyCrossStrategy : Strategy
{
private const string Parameters = "Parameters";
private const string Risk = "Risk";
[HyperionXProperty]
[Category(Parameters)]
[Display(Name = "Fast period", GroupName = Parameters, Order = 1)]
[Range(1, 200)]
public int FastPeriod { get; set; }
[HyperionXProperty]
[Category(Parameters)]
[Display(Name = "Slow period", GroupName = Parameters, Order = 2)]
[Range(2, 500)]
public int SlowPeriod { get; set; }
[HyperionXProperty]
[Category(Risk)]
[Display(Name = "Quantity", GroupName = Risk, Order = 10)]
[Range(1, 1000)]
public int Quantity { get; set; }
[HyperionXProperty]
[Category(Risk)]
[Display(Name = "Stop ticks", GroupName = Risk, Order = 11)]
[Range(1, 10000)]
public int StopTicks { get; set; }
[HyperionXProperty]
[Category(Risk)]
[Display(Name = "Target ticks", GroupName = Risk, Order = 12)]
[Range(1, 10000)]
public int TargetTicks { get; set; }
public override void OnStateChanged()
{
if (State == State.SetDefaults)
{
Name = "My Cross Strategy";
Version = "1.0";
FastPeriod = 9;
SlowPeriod = 21;
Quantity = 1;
StopTicks = 20;
TargetTicks = 40;
}
}
public override void OnBarUpdate()
{
if (CurrentBar < SlowPeriod + 1)
return;
var fast = AverageClose(FastPeriod);
var slow = AverageClose(SlowPeriod);
var priorFast = AverageClose(FastPeriod, 1);
var priorSlow = AverageClose(SlowPeriod, 1);
var crossedUp = priorFast <= priorSlow && fast > slow;
var crossedDown = priorFast >= priorSlow && fast < slow;
if (Ctx.Position.IsFlat && crossedUp)
{
SetStopLossTicks("Long Entry", StopTicks);
SetProfitTargetTicks("Long Entry", TargetTicks);
EnterLong(Quantity, "Long Entry");
}
else if (Ctx.Position.IsFlat && crossedDown)
{
SetStopLossTicks("Short Entry", StopTicks);
SetProfitTargetTicks("Short Entry", TargetTicks);
EnterShort(Quantity, "Short Entry");
}
else if (Ctx.Position.MarketPosition == MarketPosition.Long && crossedDown)
{
ExitLong(0, "Cross Exit", "Long Entry");
}
else if (Ctx.Position.MarketPosition == MarketPosition.Short && crossedUp)
{
ExitShort(0, "Cross Exit", "Short Entry");
}
}
private double AverageClose(int period, int barsAgo = 0)
{
var sum = 0.0;
for (var index = barsAgo; index < barsAgo + period; index++)
sum += Close[index];
return sum / period;
}
}
Managed order API
Use managed helpers for normal entry, exit, stop, and target workflows.
| Purpose | Helpers |
|---|---|
| Market entry | EnterLong(...), EnterShort(...) |
| Limit entry | EnterLongLimit(...), EnterShortLimit(...) |
| Stop-market entry | EnterLongStopMarket(...), EnterShortStopMarket(...) |
| Market exit | ExitLong(...), ExitShort(...) |
| Limit exit | ExitLongLimit(...), ExitShortLimit(...) |
| Stop-market exit | ExitLongStopMarket(...), ExitShortStopMarket(...) |
| Protective stop | SetStopLoss(...), SetStopLossTicks(...) |
| Profit target | SetProfitTarget(...), SetProfitTargetTicks(...) |
| Clear protection | ClearStopLoss(...), ClearProfitTarget(...) |
Passing 0 as a managed exit quantity means “resolve the available position quantity.” Use fromEntrySignal to tie an exit or protective order to a specific entry.
Ctx.Orders provides a second documented managed facade for explicit market, limit, stop, cancel, change, flatten, and OCO operations. See Strategy and Order Reference.
The inherited and Ctx.Orders protection methods use different argument order:
SetStopLossTicks("Long Entry", 20);
Ctx.Orders.SetManagedStopLossTicks(20, "Long Entry");
Prefer the inherited form in ordinary managed strategies.
Stops and targets
Register managed protection before the entry call:
SetStopLossTicks("Breakout", StopTicks);
SetProfitTargetTicks("Breakout", TargetTicks);
EnterLong(Quantity, "Breakout");
Signal names are part of the relationship. "Breakout" must match exactly. Use different names when independently managing multiple entries.
Test:
- Long and short behavior.
- Partial fills and partial exits.
- Rejected or canceled entries.
- Gaps through stop prices.
- Multiple signals on the same bar.
- Strategy disable, connection loss, and shutdown.
Position and account state
Use Ctx.Position for the strategy's current instrument position:
if (Ctx.Position.IsFlat)
{
// Entry may be considered.
}
double quantity = Ctx.Position.Quantity;
double averagePrice = Ctx.Position.AveragePrice;
MarketPosition side = Ctx.Position.MarketPosition;
Ctx.Account exposes read-only account balances, orders, and positions. Strategy position and account position are not interchangeable when manual orders, multiple strategies, or multiple accounts are involved.
Order and execution callbacks
Submission is only the start of the order lifecycle.
public override void OnOrderUpdate(Order order)
{
// Track accepted, working, canceled, rejected, and filled states.
}
public override void OnExecutionUpdate(Order order)
{
// React to executions and resulting position changes.
}
Store returned order references when you need to cancel or change a specific working order. Do not infer a fill only because an entry method returned.
Direct SubmitOrder(...)
Use the lower-level API only when managed helpers cannot express the required order lifecycle.
Order order = SubmitOrder(
selectedBarsInProgress: 0,
orderAction: OrderAction.Buy,
orderType: OrderType.Limit,
quantity: Quantity,
limitPrice: entryPrice,
stopPrice: 0,
oco: "",
signalName: "Advanced Entry");
Direct submission makes your strategy responsible for more state: working-order references, OCO identifiers, cancel/replace races, partial fills, duplicate prevention, and protective-order synchronization.
Supported order actions include Buy, BuyToCover, Sell, and SellShort. Supported types include Market, Limit, MIT, StopLimit, and StopMarket.
Account Routing And Real-Time Transition
The selected account still determines the intended route. In RealTime, supported ServerPaper and Broker accounts route through their provider connection, while LocalPaper uses the local simulator. Playback does not change the selection, but the current release-candidate build rejects submissions, changes, and cancellations for ServerPaper and Broker accounts while Playback is active. Playback strategies therefore require LocalPaper.
Before enabling a strategy, explicitly verify:
- Account name and
AccountType. - Connection and instrument.
- Quantity, order type, and price.
- Whether the provider accepts the requested order semantics.
The active release-candidate source exposes the Preview HistoricalPositionSyncMode setting:
Disabledis the default.AdoptOwnedExactMatchadopts only one exact position match owned by the strategy in its expected account/instrument scope.- With
AdoptOwnedExactMatchselected, a missing, ambiguous, differently sized, opposite-side, or unowned position is not repaired. The mismatch blocks new entries. - Synchronization does not submit a market order to recreate historical exposure and does not flatten or exit an account position.
The legacy SyncHistoricalPositionOnRealtimeStart Boolean is obsolete and maps only to exact-match adoption for compatibility. Do not design new code around it. This release-candidate surface remains Preview until the signed build is frozen and verified.
Playback does not select LocalPaper; verify it explicitly even though the current candidate rejects external-account mutations during Playback. Before a live transition, compare the strategy's expected position with the authoritative account state. Exact-match adoption is not a reconciliation or flattening service.
Reload And Runtime Failure Behavior
An enabled strategy keeps its existing object and assembly across a custom rebuild. Disable it before building. If it remained enabled during the build, toggling Enabled off and on afterward still reuses that object. Remove and add the strategy again, or reload its owning chart or strategy host, to run the newly compiled version.
Live indicator failures are automatically disabled after repeated consecutive errors. A live strategy calculation error skips that update. After ten consecutive live calculation errors, HyperionX keeps the strategy enabled but suspends new entries; exits and protective handling remain available.
Inspect AreEntriesSuspended, LastRuntimeError, ConsecutiveRuntimeErrors, and RuntimeStatusText, or use the warning shown in the Strategies window. Correct the cause and reconcile the authoritative account, position, and working orders before choosing Resume entries or calling ResumeEntriesAfterFault(). Historical and validation failures can abort their run instead of using this live policy.
Performance and research state
Strategies expose SystemPerformance and summary properties used by backtesting, validation, and optimization, including net profit, commissions, profit factor, Sharpe, drawdown, trades, and win rate.
Do not read final performance fields as though they were stable during an active run. Fitness modules receive a completed strategy run through their own contract.
Production checklist
- Guard every series and child-indicator read.
- Keep order quantity and risk inputs bounded with
[Range]. - Use managed helpers unless the strategy genuinely needs lower-level control.
- Give every entry and exit a meaningful signal name.
- Track asynchronous order states and partial fills.
- Prevent repeated actions from a condition that stays true.
- Model commission and slippage.
- Backtest, then use playback or simulation with an explicitly selected
LocalPaperaccount. - Confirm the selected account type; Playback does not select
LocalPaper, and the candidate external-mutation guard is only a backstop. - Review
HistoricalPositionSyncModebefore a live transition; exact-match adoption never creates or closes an account position. - Compare strategy position with account state deliberately.
- Disable running strategies before rebuilding, then recreate each affected strategy instance.
- Monitor runtime exceptions; correct and reconcile a strategy before resuming entries after automatic suspension.
- Test disable, reload, disconnect, and shutdown behavior.
- Never store credentials in strategy source or properties.
Continue with Strategy and Order Reference, Backtesting, and Validation.