onExecuted: react to outcomes
onExecuted: set cooldown on success only
Mirrors runner/src/plugins/08-exampleOnExecuted
What this teaches
- The onExecuted hook closes the loop: decide() returns intent, the runtime tries to submit, the hook gets called with the outcome
- You can persist trade results, learn from failures, schedule follow-ups
- "Set cooldown in onExecuted on success" is the cleaner pattern (vs exampleCooldown which sets it in decide())
New vs exampleConsensus
- Lifecycle hook reacting to result.outcome
import type { StrategyPlugin } from "@automark/runtime-core";
import { Market } from "@automark/sdk/market";
import { minutes } from "@automark/sdk/duration";
const COOLDOWN_MS = minutes(20);
export default function createExampleOnExecuted(): StrategyPlugin {
const vaultId = process.env.VAULT_ID;
if (!vaultId) throw new Error("exampleOnExecuted: VAULT_ID not set");
return {
name: "exampleOnExecuted",
vaultId,
triggers: [{ kind: "cron", everySeconds: 60 }],
async decide(ctx) {
if (ctx.vault.isFrozen) return [{ kind: "noop", reason: "frozen" }];
const lastSuccessMs = (await ctx.state.get<number>("lastSuccessMs")) ?? 0;
if (ctx.now - lastSuccessMs < COOLDOWN_MS) {
return [{ kind: "noop", reason: "cooldown (set by onExecuted)" }];
}
const btc = await Market.find({
asset: "BTC",
expiryAfterMs: ctx.now + minutes(5),
client: ctx.suiClient,
});
const p = await btc.price();
const quantity = ctx.vault.maxSinglePosition / 4n;
if (quantity === 0n) return [{ kind: "noop", reason: "no headroom" }];
return [
{
kind: "vault.mintBinary",
params: {
marketId: btc.id,
strike: btc.strikeAbove(p.forwardRaw, { pctBps: 200 }),
isUp: true,
quantity,
},
},
];
},
async onExecuted(ctx, result) {
// The runtime skips this hook entirely on dry-runs, so we never
// see `result.dryRun === true` here.
if (result.outcome === "submitted") {
// Only update cooldown after the tx actually landed on-chain
await ctx.state.set("lastSuccessMs", ctx.now);
ctx.logger.info("trade landed", {
digest: result.digest,
gasMist: result.gasUsed?.toString(),
});
} else if (result.outcome === "failed") {
// Don't update cooldown on failure — let the next tick retry sooner
ctx.logger.warn("trade failed, no cooldown set", { error: result.error });
}
},
};
}
// insights
// onExecuted gives you the space for when the order has been "dispatched": it may be submitted or it may hit some error along the way; either way, onExecuted lets you handle, diagnose, and refine your strategies.
Environment variables
- VAULT_ID