Defining Entry and Exit Rules

From Plain-Language Ideas to Executable Code

How to write clear, testable entry and exit conditions — from plain-language ideas to executable code on the Quanthop platform.

16 minIntermediate

Introduction

Every trading strategy begins with two questions: when do I get in? and when do I get out? These are the entry and exit rules — the core logic that transforms a market observation into a repeatable, testable system.

Most traders can answer these questions in vague terms. "I buy when the trend is strong." "I sell when it looks like it's reversing." But vagueness is the enemy of systematic trading. A rule that cannot be stated precisely cannot be coded. A rule that cannot be coded cannot be backtested. And a rule that cannot be backtested offers no evidence that it works — only a feeling, which is worth exactly nothing when real capital is on the line.

This article walks through the process of defining entry and exit rules that are genuinely testable — rules precise enough that a machine can execute them without human interpretation. We will start with what makes a rule clear, move through common structural patterns, and then show how those plain-language rules translate directly into code on the Quanthop platform. By the end, you will understand not just what good rules look like, but how to write and run them yourself.

What Makes a Rule Testable

The single most important property of a trading rule is unambiguity. If two people read the same rule and look at the same chart, they must reach the same conclusion about whether the condition is met. If there is any room for interpretation — "when momentum looks strong," "when the trend is clearly bullish" — the rule is not testable, because different observers will produce different results.

A testable rule specifies the exact indicator, the exact condition, and the exact threshold. "Enter long when RSI(14) crosses above 30 and the close is above EMA(200)" is testable. Every term is defined: RSI with a 14-period lookback, the crossing-above action, the level 30, the closing price, and EMA with a 200-period lookback. A computer can evaluate this on any candle and return a definitive yes or no.

The practical test is simple: can a machine execute this rule without any human judgment? If you find yourself needing to add qualifiers like "usually" or "when it looks right" or "in most cases," the rule needs to be rewritten. Systematic trading demands precision, because precision is what makes backtesting possible, and backtesting is what separates evidence-based trading from storytelling.

This does not mean every rule needs to be complicated. Some of the most robust strategies in quantitative finance use remarkably simple conditions — a single moving average crossover, an RSI level, a breakout of a Donchian channel. Simplicity and precision are not opposites. In fact, the simplest rules are often the easiest to make unambiguous, which is one of the reasons they tend to be more robust than elaborate multi-condition systems.

Vague vs Precise Rules
Not Testable
Testable
Buy when the trend looks strong
Enter long when close > EMA(200) and ADX(14) > 25
Take profit when it feels right
Exit when price reaches 2x ATR(14) above entry
Cut losses if it goes too far
Stop loss at 1.5x ATR(14) below entry price
Only trade when momentum is good
Filter: only take signals when RSI(14) is between 40 and 70
The test: Can a machine execute this rule without asking you a question? If not, it needs to be rewritten.
Precise rules produce identical results every time. Vague rules produce different results depending on who reads them.

Anatomy of an Entry Rule

An entry rule has three essential elements: the trigger, the direction, and the context. The trigger is the specific condition that fires — an indicator crossing a level, price breaking above a threshold, a pattern completing. The direction tells the system whether to go long or short. The context, often provided by a filter, determines whether the trigger should be acted on at all.

Consider a trend-following entry: "Enter long when the 9-period EMA crosses above the 21-period EMA." The trigger is the crossover event — the fast average rising above the slow one. The direction is long. In its simplest form there is no additional context filter, which means the signal fires in all market conditions. That may be acceptable for a first version, but most traders will eventually add context: "...and ADX(14) is above 20," which filters out low-trend environments where crossover signals tend to whipsaw.

The key design decision in any entry rule is specificity versus frequency. Each additional condition you add makes the entry more selective — fewer signals fire, but those that do are theoretically higher quality. The danger is that too many conditions can reduce the trade count to a point where the backtest becomes statistically unreliable, or where the conditions are collectively so narrow that they amount to curve-fitting to historical data. A good entry typically uses two or three conditions — enough to express a clear market thesis, but not so many that the system only fires under hyper-specific circumstances that may never recur.

Anatomy of an Exit Rule

If the entry gets all the attention, the exit is where the money is actually made or lost. An exit rule governs three scenarios: taking profit, cutting losses, and what happens when the original thesis is invalidated. A complete strategy addresses all three, because the market will present all three — repeatedly.

The most common exit structures fall into recognisable patterns. A fixed target closes the trade at a predetermined profit level, often expressed as a multiple of the initial risk — for example, take profit at 2x the stop loss distance. This is clean and easy to test, but it means you will exit winning trades even when they have further to run. A trailing stop follows the trade as it moves in your favour, locking in profit while allowing the trend to continue. This captures large moves but introduces more complexity and often reduces win rate. A signal-based exit closes the trade when the original entry conditions reverse — for instance, the fast EMA crossing back below the slow EMA. This ties the exit directly to the same market structure that produced the entry.

The relationship between entry and exit is structural, not cosmetic. A trend-following entry paired with a tight fixed target contradicts itself — you are trying to catch trends but cutting them short. A mean-reversion entry paired with a trailing stop is equally incoherent — you expect the price to revert to a mean, but the trailing stop implies you want it to keep going. The exit must match the thesis behind the entry. If your entry is designed to catch trends, your exit should be designed to ride them. If your entry is designed to catch reversions, your exit should capture the expected move and close quickly.

The stop loss is the one exit component that is genuinely non-negotiable. Every trade must have a defined worst-case scenario — a price level or condition at which you accept the trade is wrong and exit. Without it, a single adverse move can erase weeks or months of accumulated gains. The stop does not need to be tight, and it does not need to be a fixed number of points. It can be volatility-scaled (2x ATR), structure-based (below the recent swing low), or condition-based (RSI crosses back below a threshold). But it must exist, and it must be defined before the trade opens.

Common Entry Patterns

While there are infinite possible entry conditions, most systematic strategies draw from a handful of well-established patterns. Understanding these gives you a vocabulary for designing your own rules.

Trend following with moving average crossovers is perhaps the most widely studied systematic entry. When a short-period average crosses above a long-period average, it suggests that recent momentum has shifted bullish relative to the longer-term trend. The logic is intuitive: if the short-term direction has changed, maybe the price will continue in that direction. EMA crossovers are the most common — 9/21, 12/26, 20/50 — but the specific periods matter less than the general structure. What matters is that you are measuring a change in momentum relative to trend.

Mean reversion using oscillators operates on the opposite thesis: when an indicator like RSI reaches an extreme reading (below 30 or above 70), prices have moved "too far too fast" and are likely to revert toward the average. These entries tend to produce higher win rates but smaller average gains per trade. The critical nuance is when exactly to enter — buying the moment RSI drops below 30 often means catching a falling knife, while buying when RSI crosses back above 30 means the reversal may already be underway. That single distinction — entering at the extreme versus entering on the recovery — fundamentally changes the strategy's character.

Breakout entries trigger when price moves beyond a defined boundary — the highest high of the last N bars, a Bollinger Band, a Donchian channel. The thesis is that a breakout of a consolidation range represents a genuine shift in supply and demand. Breakout strategies often have low win rates (many breakouts fail) but large average winners (when a breakout catches a real trend, the move can be substantial). Pairing breakout entries with volatility or volume confirmation helps filter out false signals, though it also reduces the number of opportunities.

Indicator divergence looks for disagreements between price action and an oscillator — for example, price making a new high while RSI makes a lower high. This suggests weakening momentum behind the move. Divergence entries are inherently subjective unless defined very precisely, which is why they are more common in discretionary trading than in systematic strategies. They can be systematised, but they require careful definition of what constitutes a "swing high" or "swing low" in the indicator.

Common Entry Patterns
Trend Following
EMA(9) crosses above EMA(21)
Win rateLow-moderate (35-50%)
FrequencyModerate
Best inTrending markets
Mean Reversion
RSI(14) crosses above 30
Win rateHigh (55-70%)
FrequencyModerate-high
Best inRanging markets
Breakout
Close > Donchian(50) upper
Win rateLow (25-40%)
FrequencyLow
Best inPost-consolidation
Divergence
Price new high + RSI lower high
Win rateModerate (45-55%)
FrequencyLow
Best inTrend exhaustion
Note: Win rates and frequencies are typical ranges, not guarantees. Each pattern's character should match your exit strategy.
Different entry patterns produce different trade distributions. Choose the pattern that matches your thesis.

Common Exit Patterns

Exits shape the return distribution of your strategy more than any other single component. The same set of entry signals can produce wildly different results depending on how you exit, which is why exit design deserves at least as much thought as entry design — probably more.

Fixed risk-reward targets define the exit as a ratio of the initial risk. If your stop loss is 100 points below entry, a 2:1 target sets the take-profit 200 points above entry. This approach is clean and mathematically tractable — you know exactly what each trade can gain or lose before it opens. The trade-off is that you cap your upside, which hurts in strongly trending markets where a trailing approach would have captured far more.

Trailing stops move with the trade as it becomes profitable, typically following the highest high by a fixed distance or an ATR multiple. This is the standard exit for trend-following strategies because it lets winners run while still protecting accumulated gains. The downside is volatility — in choppy markets, a trailing stop will often get hit by normal fluctuations and close trades that would have recovered. ATR-based trailing stops (e.g., 2x ATR below the highest high since entry) adapt to current market conditions, which generally makes them more robust than fixed-point trailing stops.

Signal reversals close the trade when the entry condition reverses — the fast EMA crosses back below the slow EMA, RSI exits overbought territory, the price falls back inside the Donchian channel. This ties the exit directly to the same market structure that justified the entry, which has an appealing logical consistency. The entry said "conditions favour this trade." The exit says "those conditions are no longer true." The risk is that the reversal signal can sometimes come late, giving back a significant portion of open profit before triggering.

Time-based exits close the trade after a fixed number of bars, regardless of profit or loss. This sounds crude, but it can be surprisingly effective for mean-reversion strategies where you expect the move to happen within a specific window. If the thesis is "RSI oversold conditions tend to resolve within 5-10 bars," then a time-based exit naturally aligns with that thesis. It also prevents capital from being tied up in trades that are not moving.

From Plain Language to Code

The bridge between a trading idea and a working strategy is translation — taking a rule you can express in plain language and rewriting it in a form that a backtesting engine can execute. This is where many traders stall. They have a clear idea in their head, but turning it into code feels like an obstacle.

On the Quanthop platform, strategies are written in JavaScript using a straightforward lifecycle pattern. You do not need to be a programmer — the structure is designed to mirror how traders think. There are three functions: define() declares your adjustable parameters, init() sets up your indicators, and onBar() contains your trading logic and runs once for each candle in the dataset.

The translation process works like this. Start with a plain-language rule: "Enter long when the 9-period EMA crosses above the 21-period EMA." In code, that becomes a call to q.crossOver() comparing two indicator arrays at the current bar index. "Exit when price closes below the 20-period low" becomes a comparison between ctx.series.close[i] and a Donchian channel's lower band. Every plain-language condition has a direct code equivalent — the platform provides the indicators, the price data, and the helper functions. You provide the logic.

The critical advantage of this translation is that once your rules are in code, they are perfectly unambiguous. There is no room for interpretation. The machine will execute the rules exactly as written on every single bar in the dataset, which means the backtest results reflect the rules you actually defined — not a loose approximation of something you had in mind. This is both the power and the discipline of systematic trading: the code does not lie, and it does not rationalise.

Strategy Lifecycle: define → init → onBar
define()Once at load
Declare adjustable parameters like period lengths and thresholds.
ctx.param("fastLen", 9, { min: 5, max: 50 });
init()Once at start
Create indicators. They update automatically each bar.
ctx.indicator("ema", "ema", { period: ctx.p.fastLen });
onBar()Every bar
Run entry and exit checks. Called once per candle.
if (q.crossOver(ctx.ind.ema, ctx.ind.sma)) ctx.order.market("LONG");
onBar() repeats for every candle in the dataset or incoming live bar.
Every strategy follows this three-phase lifecycle. Parameters and indicators are set up once; your trading logic runs on each new bar.

Writing Your First Strategy

Let us walk through a complete example — an EMA crossover strategy that goes from plain-language idea to runnable code. The thesis is simple: when short-term momentum turns bullish (fast EMA crosses above slow EMA), go long. When it turns bearish, close the position.

First, the define() function declares the parameters — the knobs you can tune later during optimisation:

function define(ctx) {
ctx.param('fastLength', {
  type: 'int', label: 'Fast EMA', default: 9,
  min: 5, max: 50, optimize: true, group: 'Trend'
});
ctx.param('slowLength', {
  type: 'int', label: 'Slow EMA', default: 21,
  min: 10, max: 200, optimize: true, group: 'Trend'
});
}

These parameters have default values that work out of the box, but because optimize: true is set, the platform can automatically search for better values during Walk-Forward Analysis. The min/max bounds prevent the optimiser from exploring absurd values.

Next, init() declares the indicators — telling the engine what to calculate before the bar-by-bar simulation begins:

function init(ctx) {
ctx.indicator('fastEma', 'EMA', {
  period: ctx.p.fastLength, source: 'close'
});
ctx.indicator('slowEma', 'EMA', {
  period: ctx.p.slowLength, source: 'close'
});
}

Notice ctx.p.fastLength — the indicator period references the parameter, so when the optimiser changes the parameter value, the indicator automatically recalculates. This is how parameters and indicators stay connected.

Finally, onBar() contains the actual trading logic — the rules that fire on every candle:

function onBar(ctx, i) {
const fast = ctx.ind.fastEma;
const slow = ctx.ind.slowEma;

if (q.isNaN(fast[i]) || q.isNaN(slow[i])) return;

const pos = ctx.position('ASSET');

if (q.crossOver(fast, slow, i) && pos.qty === 0) {
  ctx.order.market('ASSET', 1, { signal: 'buy' });
}

if (q.crossUnder(fast, slow, i) && pos.qty > 0) {
  ctx.order.close('ASSET', { signal: 'sell' });
}
}

The q.isNaN() check skips bars where the indicators have not had enough data to compute — the warm-up period. After that, the logic maps directly to the plain-language rules: if the fast EMA crosses above the slow and we have no position, buy. If it crosses below and we are long, close. Every line has a clear purpose, and the entire strategy fits in under 30 lines of code.

Adding Stops and Targets

The basic crossover strategy above has a signal-based exit — close when the crossover reverses. But as we discussed, relying on a single exit mechanism leaves gaps. What happens if the price drops sharply before the crossover reverses? The strategy has no stop loss, which means a sudden crash could erase unrealised gains entirely.

Adding a volatility-based stop loss is straightforward. Declare an ATR indicator in init() and use it to calculate a stop price when the trade opens:

function init(ctx) {
ctx.indicator('fastEma', 'EMA', { period: ctx.p.fastLength, source: 'close' });
ctx.indicator('slowEma', 'EMA', { period: ctx.p.slowLength, source: 'close' });
ctx.indicator('atr', 'ATR', { period: 14 });
ctx.state.stopPrice = null;
}

function onBar(ctx, i) {
const fast = ctx.ind.fastEma;
const slow = ctx.ind.slowEma;
const atr = ctx.ind.atr[i];

if (q.isNaN(fast[i]) || q.isNaN(slow[i]) || q.isNaN(atr)) return;

const pos = ctx.position('ASSET');
const close = ctx.series.close[i];

// Entry with stop calculation
if (q.crossOver(fast, slow, i) && pos.qty === 0) {
  ctx.order.market('ASSET', 1, { signal: 'buy' });
  ctx.state.stopPrice = close - 2 * atr;
}

// Stop loss check
if (pos.qty > 0 && ctx.state.stopPrice && close < ctx.state.stopPrice) {
  ctx.order.close('ASSET', { signal: 'stop' });
  ctx.state.stopPrice = null;
}

// Signal-based exit (crossover reversal)
if (q.crossUnder(fast, slow, i) && pos.qty > 0) {
  ctx.order.close('ASSET', { signal: 'sell' });
  ctx.state.stopPrice = null;
}
}

The stop is set at 2x ATR below the entry price, which means it adapts to current market volatility. In calm markets the stop is tighter; in volatile markets it gives the trade more room to breathe. The ctx.state object persists between bars, so the stop price calculated at entry carries forward until the trade closes.

You could extend this further with a trailing stop — updating ctx.state.stopPrice upward as the trade moves in your favour — or add a fixed target for profit-taking. The point is not to demonstrate every possible exit mechanism, but to show that the code structure makes it natural to layer multiple exit conditions onto the same strategy. Each condition sits alongside the others in onBar(), and the engine evaluates all of them on every bar.

Combining Entry Conditions

A single-condition entry is a useful starting point, but most production strategies combine two or three conditions to filter noise and improve signal quality. The art of combining conditions is finding the balance between selectivity and trade frequency — enough conditions to express a meaningful thesis, but not so many that you over-fit to historical data or reduce your sample to statistically meaningless levels.

The most common combination pattern is trend plus trigger. A long-term indicator establishes the overall market regime — is the trend up or down? A short-term indicator provides the specific entry timing within that regime. For example: "Only take long signals when price is above EMA(200)" establishes the trend filter, and "Enter when RSI(14) crosses above 30" provides the trigger. In code, this is simply an && condition:

if (ctx.series.close[i] > ctx.ind.trendEma[i]
  && q.crossOver(ctx.ind.rsi, 30, i)
  && pos.qty === 0) {
ctx.order.market('ASSET', 1, { signal: 'buy' });
}

Another effective pattern is confirmation — requiring two independent indicators to agree before entering. An EMA crossover confirmed by MACD histogram turning positive, for example, or a Bollinger Band breakout confirmed by rising volume. The logic behind confirmation is that if two unrelated measures of market state both agree, the signal is more likely to reflect a genuine shift rather than random noise.

The danger zone begins when you start adding a fourth, fifth, or sixth condition. Each additional filter looks good in a backtest because it removes some losing trades — but it also removes winning trades, and the conditions you are selecting for may be specific to the historical data rather than reflective of enduring market structure. If your strategy only fires 15 times in three years of data because the conditions are so narrow, the backtest results are statistically meaningless regardless of how good they look. As a rough guide, aim for at least 100 trades in your backtest sample to draw any meaningful conclusions about the strategy's edge.

Common Mistakes in Rule Design

The most common mistake is vague conditions — rules that sound clear in your head but are impossible to execute systematically. "Buy when the trend is strong" is not a rule. "Buy when ADX(14) is above 25 and the close is above EMA(50)" is a rule. Every time you write a condition, apply the machine test: could a computer execute this without asking you a question? If the answer is no, keep refining.

The second mistake is mismatched entry and exit logic. Your entry thesis and exit mechanism need to be structurally compatible. If you enter on a mean-reversion signal expecting a quick bounce, a trailing stop designed to ride long trends makes no sense — the trade will either hit the target quickly or the thesis was wrong, and the trailing logic will just delay the inevitable loss. Match trend entries with trend exits. Match reversion entries with reversion exits. The system should be internally coherent.

The third mistake is not defining all scenarios. What happens when your entry fires but you are already in a position? What if two signals fire on the same bar? What happens at the end of the data series — do open positions get closed or left hanging? These edge cases may seem minor, but they subtly distort backtest results if left undefined. The Quanthop engine handles many of these automatically — orders fill at the next bar's open, position checks prevent duplicate entries — but it is important to understand the assumptions your platform makes so that your rules account for them.

The fourth and most insidious mistake is complexity masquerading as sophistication. A strategy with eight indicators and twelve conditions is not sophisticated — it is fragile. Each additional parameter is a degree of freedom that the optimiser can exploit to fit historical noise. The most robust systematic strategies in quantitative finance tend to be surprisingly simple in their rule structure. The edge comes from the system design — how entries, exits, sizing, and risk management interact — not from the sheer number of conditions.

Testing and Iterating

Writing rules is only the beginning. The real work starts when you run those rules against historical data and examine what comes back. A backtest tells you what would have happened if these exact rules had been followed on every bar in the dataset — and the results are often humbling.

The first thing to examine is not the profit or return, but the trade count and distribution. How many trades did the strategy produce? If the answer is fewer than 50 or 60, the results are not statistically significant — you do not have enough data points to distinguish skill from luck. Look at the distribution of wins and losses: is the strategy winning big on a few trades and losing small on many (trend-following character), or winning often but occasionally losing big (mean-reversion character)? This distribution should match your thesis. If it does not, something in your rules is not behaving as you expected.

Next, question every result that looks too good. If your first backtest shows a 90% win rate or a Sharpe ratio above 3, something is almost certainly wrong — either the rules have a look-ahead bias, the sample is too small, or the conditions are over-fitted to the specific data period. Pull the strategy apart. Remove conditions one at a time and see what changes. This process of subtraction is often more revealing than the original construction.

Finally, remember that a backtest on a single data period is only the first step toward validation. Rules that work on one period need to be tested on out-of-sample data, across different market conditions, and ideally across multiple assets. Walk-Forward Analysis automates this process by splitting the data into training and testing windows, optimising parameters on the training set, and then evaluating on the unseen test set. If your rules contain a genuine edge, they will show some degree of consistency across these windows. If they do not, the edge was probably an artefact of the data you happened to test on.

Final Thought

Good entry and exit rules have a paradoxical quality: they are simple enough to explain in a sentence, but precise enough that a machine can execute them without ambiguity. "Go long when short-term momentum crosses above long-term trend" is a sentence. The code that expresses it — two EMAs, a crossover check, an ATR-based stop — is perhaps twenty lines. The distance between the idea and the implementation is shorter than most people expect.

The real challenge is not writing rules. It is writing rules that are honest — rules that reflect a genuine market thesis rather than a pattern you noticed on a single chart. It is resisting the urge to add conditions until the backtest looks perfect, because perfection in a backtest almost always means fragility in live markets. And it is accepting that even well-designed rules will produce losses, drawdowns, and periods of underperformance, because that is the nature of probabilistic systems operating in uncertain environments.

Start simple. Write two or three clear conditions. Code them. Backtest them. Examine the results with scepticism rather than hope. Then iterate — not toward complexity, but toward clarity.

Related articles

Browse all learning paths