Write your first strategy
This page is for developers who want to write their own strategy in Rust. To run the ready shot strategy, you do not need any of this: see Getting started.
Prerequisites
- Rust (edition 2021)
- A tradectl account (sign in at app.tradectl.com with Google or GitHub)
1. Install the CLI
curl -fsSL https://tradectl.com/install.sh | shSee the installation guide for manual download options and PATH setup.
2. Log in
Create an API key on your account page (app.tradectl.com/account, API Keys), then:
echo "st_live_<your-key>" | tradectl login3. Scaffold a strategy
tradectl init my-strategy
cd my-strategyThis creates a Rust crate with:
my-strategy/
├── Cargo.toml
├── config.json # Exchange config and strategy parameters
├── src/
│ ├── lib.rs # Strategy implementation
│ └── params.rs # Parameter definitions
├── tests/
│ └── test.rs # Unit test
├── data/ # Backtest data files
├── README.md
├── STRATEGY.md # Optional — surfaced via MCP / AI agent
└── .gitignore4. Write your strategy
Edit src/lib.rs. The scaffold uses the current Action enum (v2):
use tradectl_sdk::{
Strategy, Action, OrderKind, ExitOrder, ExitType,
Side, TickerEvent, StrategyContext, Params, ParamDef,
};
tradectl_sdk::declare_strategy!("my_strategy", MyStrategy::new);
pub struct MyStrategy {
order_size: f64,
tp_pct: f64,
sl_pct: f64,
}
impl MyStrategy {
pub fn new(params: &Params) -> Self {
Self {
order_size: params.get("order_size", 0.001),
tp_pct: params.get("tp_pct", 0.5),
sl_pct: params.get("sl_pct", 1.0),
}
}
}
impl Strategy for MyStrategy {
fn name(&self) -> &str { "my_strategy" }
fn describe(&self) -> &str { "My first strategy" }
fn on_ticker(&mut self, ticker: &TickerEvent, ctx: &StrategyContext) -> Action {
// Honor the gate. The runner sets ctx.can_enter = false when the
// strategy is blocked by triggers, sessions, penalties, etc.
if !ctx.can_enter || !ctx.positions.is_empty() {
return Action::Hold;
}
// Place a market entry with TP (limit) and SL (stop, 3s delay).
let entry = ticker.ask_price;
let tp = entry * (1.0 + self.tp_pct / 100.0);
let sl = entry * (1.0 - self.sl_pct / 100.0);
Action::PlaceEntry {
side: Side::Long,
price: None, // None = market; Some(p) = limit
size: self.order_size,
kind: OrderKind::Market,
exits: vec![
ExitOrder { id: "tp".into(), price: tp, size: self.order_size, kind: ExitType::Limit, delay_ms: 0 },
ExitOrder { id: "sl".into(), price: sl, size: self.order_size, kind: ExitType::Stop, delay_ms: 3000 },
],
entry_id: None,
}
}
fn params_schema(&self) -> Vec<ParamDef> {
MyStrategyParams::schema()
}
}The full Action enum has nine variants — see Strategies and Strategy Trait:
HoldPlaceEntry { side, price, size, kind, exits, entry_id }EditEntry,CancelEntrySetExits,AddExit,UpdateExit,RemoveExitCloseAll
5. Test
cargo test6. Build
tradectl buildProduces target/release/libmy_strategy.so (or .dylib on macOS). Cargo replaces dashes with underscores in the cdylib name.
7. Run (paper trading)
# Edit config.json — set isEmulator: true for paper, false for live
tradectl run my-bot -c config.jsonmy-bot is the bot's name. -c copies config.json into the bot's own folder, ~/.tradectl/bot/my-bot/, and the bot uses that copy from then on.
Paper mode connects to the live exchange WebSocket for real market data but simulates fills locally — no exchange API keys needed.
Next steps
- Strategies: the Strategy trait, the Action enum, and managers
- Backtesting: run backtests on historical data
- Optimization: parameter sweeps with the BatchStrategy trait
- Run from the command line: run and stop bots with the CLI