Triggers
Triggers let strategies communicate with each other without knowing about each other. One strategy fires a signal, and other strategies react to it — opening trades, blocking trades, or force-closing positions.
This is how you build multi-strategy setups: a paper-trading "emulator" strategy proves a signal is good, fires a trigger, and a real-money strategy acts on it.
How It Works
The trigger system has three roles a strategy can play:
- Producer — fires trigger keys when positions close at profit or loss
- Consumer — only trades when specific trigger keys are active
- Blacklisted — gets blocked (and optionally force-closed) when specific keys fire
A single strategy can play multiple roles at the same time.
Strategy A (producer + emulator)
position closes at profit → activates key "emu_profit" for 120s
position closes at loss → activates key "emu_loss" for 120s
Strategy B (consumer + blacklisted)
sees "emu_profit" active → allowed to trade
sees "emu_loss" fire → blacklisted for 1 hour, positions force-closed
no triggers active → blocked (waits for signal)Trigger Keys
A trigger key is a string identifier — any name you choose. Keys are global: when one strategy activates "emu_profit", every other strategy in the bot can see it.
Keys have a duration. When activated, a key stays active for the configured number of seconds, then expires automatically. Activating the same key again resets its timer.
Key "emu_profit" activated at 14:00 (duration: 120s)
→ active from 14:00 to 14:02
→ re-activated at 14:01 → now active until 14:03 (timer restarted)
→ expires at 14:03 → no longer activeEnabling Triggers
Add a triggers object to your strategy parameters:
{
"triggers": {
"triggerKeyProfit": "emu_profit",
"triggerKeyLoss": "emu_loss",
"triggerSeconds": 120,
"triggerScope": "global",
"triggerByKeys": ["emu_profit"],
"blacklistKeys": ["emu_loss"],
"blacklistSeconds": 3600,
"forceCloseKeys": ["emu_loss"],
"forceCancelKeys": ["emu_loss"]
}
}If no triggers key is present in the strategy params, triggers are disabled for that strategy.
Producing Triggers
A producer strategy fires trigger keys when its positions close.
| Parameter | Type | Default | Description |
|---|---|---|---|
triggerKeyProfit | string | none | Key to activate when a position closes at profit |
triggerKeyLoss | string | none | Key to activate when a position closes at loss |
triggerSeconds | seconds | 60 | How long the activated key stays active |
triggerScope | string | "global" | "global" or "symbol" — stored metadata, see Scope below |
A strategy is a producer if it has triggerKeyProfit or triggerKeyLoss set.
Example: Emulator Producer
{
"triggers": {
"triggerKeyProfit": "emu_profit",
"triggerKeyLoss": "emu_loss",
"triggerSeconds": 120,
"triggerScope": "global"
}
}This strategy fires "emu_profit" (active for 2 minutes) when it closes a profitable position, and "emu_loss" when it closes a losing position.
Consuming Triggers
A consumer strategy only trades when specific trigger keys are active. If none of its required keys are active, can_trade() returns false and the strategy holds.
| Parameter | Type | Default | Description |
|---|---|---|---|
triggerByKeys | string[] | [] | Keys that allow trading. OR logic: any one active = trading allowed |
Empty triggerByKeys means the strategy always trades (no gating).
OR Logic
If you list multiple keys in triggerByKeys, any single key being active is enough to trade. You don't need all of them.
triggerByKeys: ["emu_profit", "manual_go"]
"emu_profit" active, "manual_go" expired → CAN trade (one is enough)
"emu_profit" expired, "manual_go" active → CAN trade
both active → CAN trade
both expired → CANNOT tradeExample: Gated Consumer
{
"triggers": {
"triggerByKeys": ["emu_profit"]
}
}This strategy only opens positions when the "emu_profit" key is active. When the key expires, the strategy goes back to holding.
Blacklisting
A blacklisted strategy gets temporarily blocked when specific trigger keys fire. Unlike consumers (which need triggers to trade), blacklisted strategies are blocked by triggers.
| Parameter | Type | Default | Description |
|---|---|---|---|
blacklistKeys | string[] | [] | Keys that block this strategy when they fire |
blacklistSeconds | seconds | 3600 | How long the strategy stays blocked |
When a key in blacklistKeys fires, the strategy is blocked on all symbols it trades for blacklistSeconds. The block is per strategy+symbol — other strategies are not affected.
Strategy B has blacklistKeys: ["emu_loss"]
Strategy B trades: BTCUSDT, ETHUSDT, SOLUSDT
"emu_loss" fires →
Strategy B blocked on BTCUSDT for 1 hour
Strategy B blocked on ETHUSDT for 1 hour
Strategy B blocked on SOLUSDT for 1 hour
Strategy A (different strategy) → NOT blockedForce-Close and Force-Cancel
When blacklisted, you can optionally force-close positions and cancel pending orders:
| Parameter | Type | Default | Description |
|---|---|---|---|
forceCloseKeys | string[] | [] | Keys that force-close all positions when they fire |
forceCancelKeys | string[] | [] | Keys that cancel all pending orders when they fire |
These are one-time actions — they execute once when the blacklist activates. If the strategy opens a new position later (after the blacklist expires), it won't be auto-closed.
forceCloseKeys: ["emu_loss"]
"emu_loss" fires →
1. Strategy blacklisted on all symbols
2. All open positions force-closed (once)
3. All pending orders cancelled (if forceCancelKeys also set)
4. Strategy blocked for blacklistSecondsExample: Full Consumer + Blacklist
{
"triggers": {
"triggerByKeys": ["emu_profit"],
"blacklistKeys": ["emu_loss"],
"blacklistSeconds": 3600,
"forceCloseKeys": ["emu_loss"],
"forceCancelKeys": ["emu_loss"]
}
}This strategy:
- Only trades when
"emu_profit"is active - Gets blocked for 1 hour when
"emu_loss"fires - Force-closes all positions and cancels all orders on blacklist
Clearing Keys
Producers can clear other trigger keys when they fire. This lets one strategy cancel another strategy's signal.
| Parameter | Type | Default | Description |
|---|---|---|---|
clearOnProfitKeys | string[] | [] | Keys to deactivate when this strategy fires a profit trigger |
clearOnLossKeys | string[] | [] | Keys to deactivate when this strategy fires a loss trigger |
Strategy A config:
triggerKeyProfit: "a_profit"
clearOnProfitKeys: ["b_signal"]
Strategy A closes at profit →
1. Activates "a_profit"
2. Deactivates "b_signal" (cleared)Scope
Each trigger has a scope that records whether it was fired globally or for a specific symbol:
"global"— the trigger applies to all symbols"symbol"— the trigger was fired for the specific symbol where the position closed
Scope is stored as metadata on the trigger. When checking if a key is active (is_key_active, are_keys_satisfied), scope is not checked. Once a key is active, it gates all symbols regardless of scope. Scope is informational — useful for logging and monitoring.
| Parameter | Type | Default | Description |
|---|---|---|---|
triggerScope | string | "global" | "global" or "symbol" |
Trade Gating Flow
On every tick, the runner checks whether a strategy is allowed to trade:
tick arrives →
1. Is strategy blacklisted on this symbol?
YES → Action::Hold (blocked)
2. Does strategy have triggerByKeys configured?
NO → allowed (no gating)
YES → is ANY key in triggerByKeys active?
YES → allowed
NO → Action::Hold (waiting for signal)
3. Check session (if sessions enabled)
4. Run strategy logicBlacklist is checked first and takes priority over everything else.
Cleanup
Expired triggers and blacklist entries are automatically cleaned up every 5 seconds by the runner. You don't need to manage expiration — it happens in the background.
Multi-Strategy Example
A common pattern: emulator strategy proves signals on paper, real strategy only trades on confirmed signals.
Strategy 1: Emulator (paper trading, produces triggers)
{
"triggers": {
"triggerKeyProfit": "emu_profit",
"triggerKeyLoss": "emu_loss",
"triggerSeconds": 120,
"triggerScope": "global"
}
}Strategy 2: Real trader (consumes triggers, blacklisted by losses)
{
"triggers": {
"triggerByKeys": ["emu_profit"],
"blacklistKeys": ["emu_loss"],
"blacklistSeconds": 3600,
"forceCloseKeys": ["emu_loss"],
"forceCancelKeys": ["emu_loss"],
"clearOnProfitKeys": [],
"clearOnLossKeys": []
}
}How this plays out:
- Emulator trades on paper. No real money involved.
- Emulator closes at profit → fires
"emu_profit"(active for 2 minutes) - Real trader sees
"emu_profit"→ starts trading with real money - 2 minutes pass,
"emu_profit"expires → real trader goes back to holding - Emulator closes at loss → fires
"emu_loss" - Real trader gets blacklisted for 1 hour, all positions force-closed
- After 1 hour, blacklist expires → real trader waits for next
"emu_profit"
All Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
triggerKeyProfit | string | none | Key activated on profitable close |
triggerKeyLoss | string | none | Key activated on losing close |
triggerSeconds | seconds | 60 | Duration keys stay active |
triggerScope | string | "global" | Scope metadata: "global" or "symbol" |
triggerByKeys | string[] | [] | Keys required to trade (OR logic) |
blacklistKeys | string[] | [] | Keys that block this strategy |
blacklistSeconds | seconds | 3600 | Blacklist duration |
forceCloseKeys | string[] | [] | Keys that force-close positions |
forceCancelKeys | string[] | [] | Keys that cancel pending orders |
clearOnProfitKeys | string[] | [] | Keys to clear on profit trigger |
clearOnLossKeys | string[] | [] | Keys to clear on loss trigger |
