Trading Modes
Quanthop supports three trading modes that control how your strategy enters and exits positions. Each mode must be natively implemented in your strategy code — the engine does not auto-invert or transform signals.
Overview
| Mode | Description | When to Use |
|---|---|---|
| Long-only | Buy low, sell high | Default. Trend-following, mean-reversion on dips |
| Short-only | Sell high, buy back lower | Bearish strategies, hedging |
| Bidirectional | Both long and short, always in market | Momentum strategies, regime-switching |
How It Works
Your strategy receives the selected trading mode via ctx.p.tradingMode. Based on this value, your onBar function decides which orders to place. If your strategy only handles LONG_ONLY, the Short-only and Bidirectional options will be locked in the UI.
Long-only (Default)
The simplest and most common mode. Your strategy buys with a positive quantity and closes the position to exit. If your strategy does not check ctx.p.tradingMode, it operates as long-only by default.
function onBar(ctx, i) { const fast = ctx.ind.fastEma[i]; const slow = ctx.ind.slowEma[i]; const pos = ctx.position('ASSET'); // Enter long when fast EMA crosses above slow EMA if (q.crossOver(ctx.ind.fastEma, ctx.ind.slowEma, i) && pos.qty === 0) { ctx.order.market('ASSET', 1, { signal: 'buy' }); } // Exit long when fast EMA crosses below slow EMA if (q.crossUnder(ctx.ind.fastEma, ctx.ind.slowEma, i) && pos.qty > 0) { ctx.order.close('ASSET', { signal: 'sell' }); } }
Key points:
- Quantity of
1means "use full allocation" for a long entry ctx.order.close()exits the entire position- Position is flat (qty === 0) when not in a trade
Short-only
Short-only strategies profit when prices fall. You enter a short position with a negative quantity (-1) and close it to exit.
function onBar(ctx, i) { const fast = ctx.ind.fastEma[i]; const slow = ctx.ind.slowEma[i]; const pos = ctx.position('ASSET'); // Enter short when fast EMA crosses below slow EMA if (q.crossUnder(ctx.ind.fastEma, ctx.ind.slowEma, i) && pos.qty === 0) { ctx.order.market('ASSET', -1, { signal: 'enterShort' }); } // Exit short when fast EMA crosses above slow EMA if (q.crossOver(ctx.ind.fastEma, ctx.ind.slowEma, i) && pos.qty < 0) { ctx.order.close('ASSET', { signal: 'exitShort' }); } }
Key points:
- Quantity of
-1opens a short position (sell first, buy back later) pos.qty < 0indicates an open short positionctx.order.close()buys back to close the short- Short P&L is positive when exit price < entry price
Bidirectional
Bidirectional strategies can hold both long and short positions, flipping between them as the market changes. This mode is for strategies that are always in the market.
function onBar(ctx, i) { const fast = ctx.ind.fastEma[i]; const slow = ctx.ind.slowEma[i]; const pos = ctx.position('ASSET'); // Bullish signal: close any short, enter long if (q.crossOver(ctx.ind.fastEma, ctx.ind.slowEma, i)) { if (pos.qty < 0) { ctx.order.close('ASSET', { signal: 'exitShort' }); } ctx.order.market('ASSET', 1, { signal: 'buy' }); } // Bearish signal: close any long, enter short if (q.crossUnder(ctx.ind.fastEma, ctx.ind.slowEma, i)) { if (pos.qty > 0) { ctx.order.close('ASSET', { signal: 'sell' }); } ctx.order.market('ASSET', -1, { signal: 'enterShort' }); } }
Key points:
- Always close the opposing position before opening a new one
- Check
pos.qty > 0(long) orpos.qty < 0(short) to know current state - The strategy flips from long to short (or vice versa) on each signal
- More trades than long-only, so watch for excessive fees in choppy markets
Handling Multiple Modes in One Strategy
To support multiple modes, read ctx.p.tradingMode and branch your logic accordingly. This is the recommended approach for versatile strategies:
function onBar(ctx, i) { const tradingMode = ctx.p.tradingMode || 'LONG_ONLY'; const pos = ctx.position('ASSET'); // Compute your signals const bullish = q.crossOver(ctx.ind.fastEma, ctx.ind.slowEma, i); const bearish = q.crossUnder(ctx.ind.fastEma, ctx.ind.slowEma, i); if (tradingMode === 'LONG_ONLY') { if (bullish && pos.qty === 0) { ctx.order.market('ASSET', 1, { signal: 'buy' }); } if (bearish && pos.qty > 0) { ctx.order.close('ASSET', { signal: 'sell' }); } } else if (tradingMode === 'SHORT_ONLY') { if (bearish && pos.qty === 0) { ctx.order.market('ASSET', -1, { signal: 'enterShort' }); } if (bullish && pos.qty < 0) { ctx.order.close('ASSET', { signal: 'exitShort' }); } } else if (tradingMode === 'BIDIRECTIONAL') { if (bullish) { if (pos.qty < 0) ctx.order.close('ASSET', { signal: 'exitShort' }); ctx.order.market('ASSET', 1, { signal: 'buy' }); } if (bearish) { if (pos.qty > 0) ctx.order.close('ASSET', { signal: 'sell' }); ctx.order.market('ASSET', -1, { signal: 'enterShort' }); } } }
Declaring Supported Modes
When you create a strategy from a template, the supported modes are defined in the template metadata. Only modes listed in supportedTradingModes will be selectable in the Direction dropdown.
// Template supports all three modes supportedTradingModes: ['LONG_ONLY', 'SHORT_ONLY', 'BIDIRECTIONAL'] // Template supports long-only (default if omitted) supportedTradingModes: ['LONG_ONLY']
If your strategy code only handles long entries — don't declare SHORT_ONLY or BIDIRECTIONAL. The UI will show a lock icon on unsupported modes to prevent misconfiguration.
Signal Names
Use descriptive signal names so your backtest results are easy to read:
| Action | Signal Name | Order |
|---|---|---|
| Enter long | 'buy' or 'enterLong' | ctx.order.market('ASSET', 1, { signal: 'buy' }) |
| Exit long | 'sell' or 'exitLong' | ctx.order.close('ASSET', { signal: 'sell' }) |
| Enter short | 'enterShort' or 'short' | ctx.order.market('ASSET', -1, { signal: 'enterShort' }) |
| Exit short | 'exitShort' or 'coverShort' | ctx.order.close('ASSET', { signal: 'exitShort' }) |
Partial Position Sizing
Both long and short modes support partial positions:
// Partial long entry (50% of capital) ctx.order.market('ASSET', 0.5, { signal: 'partialBuy' }); // Partial short entry (50% of capital) ctx.order.market('ASSET', -0.5, { signal: 'partialShort' }); // Reduce long position by 25% ctx.order.reduce('ASSET', 0.25, { signal: 'takeProfit' }); // Reduce short position by 25% ctx.order.reduce('ASSET', 0.25, { signal: 'partialCover' });
Checking Position State
Use ctx.position() to check where you are before placing orders:
const pos = ctx.position('ASSET'); pos.qty === 0 // Flat (no position) pos.qty > 0 // Long position open pos.qty < 0 // Short position open pos.avgPrice // Average entry price pos.unrealized // Current P&L (unrealized)
Common Mistakes
1. Forgetting to close before flipping (bidirectional)
// Wrong: opens short while still long ctx.order.market('ASSET', -1, { signal: 'enterShort' }); // Correct: close long first, then enter short if (pos.qty > 0) ctx.order.close('ASSET', { signal: 'sell' }); ctx.order.market('ASSET', -1, { signal: 'enterShort' });
2. Using positive quantity for shorts
// Wrong: this opens a long position ctx.order.market('ASSET', 1, { signal: 'enterShort' }); // Correct: use negative quantity ctx.order.market('ASSET', -1, { signal: 'enterShort' });
3. Not checking current position before entry
// Wrong: may stack multiple entries if (bullish) ctx.order.market('ASSET', 1, { signal: 'buy' }); // Correct: only enter when flat if (bullish && pos.qty === 0) { ctx.order.market('ASSET', 1, { signal: 'buy' }); }