Strategies Showcase
Real strategies shipped in the user/strategies folder of the Planar.jl repository — from a bare template to margin trading with streaming indicators. The same code runs in backtest, paper, and live mode.
BareStrat
The base template every Planar strategy extends
BitMEX
Isolated
1m
module BareStrat
using ..Strategies: Strategies as st
using .st, .st.ExchangeTypes
using .st.TimeTicks
import .st: call!
using .st.Misc: Sim, NoMargin, Paper
using .st: Buy, Sell
using .st.OrderTypes: MarketOrder, ShortMarketOrder
const DESCRIPTION = "BaseStrat"
const EXC = :bitmex
const S{M} = Strategy{M,nameof(@__MODULE__),typeof(EXCID),Isolated}
function call!(s::T, ts::DateTime, ctx) where {T<:SC}
foreach(s.universe) do ai
oside = rand((Buy, Sell))
pside = rand((Long, Short))
if isopen(ai)
call!(s, ai, ordertp(ai, oside, pside); amount=float(ai) / 3, date)
elseif cash(s) > ai.limits.cost.min
call!(s, ai, ordertp(ai, oside, pside); amount=ai.limits.amount.min, date)
end
end
end
function call!(::Type{<:SC}, ::StrategyMarkets)
["BTC/USDT:USDT", "ETH/USDT:USDT", "SOL/USDT:USDT"]
end
endSimpleStrategy
Moving-average crossover on daily BTC
Binance
NoMargin
1d
function call!(s::SC, ts::DateTime, _)
ats = available(s.timeframe, ts)
foreach(s.universe) do ai
df = ohlcv(ai)
idx = dateindex(df, ats)
if idx > 15
ma7d = mean(@view df.close[(idx - 7):idx])
ma15d = mean(@view df.close[(idx - 15):idx])
side = ifelse(ma7d > ma15d, Buy, Sell)
call!(s, ai, MarketOrder{side}; date=ts, amount=0.001)
end
end
end
const ASSETS = ["BTC/USDT"]
function call!(::Union{<:SC,Type{<:SC}}, ::StrategyMarkets)
ASSETS
endTwoIntervals
Dual-timeframe trend following with EMA + RSI
Binance
NoMargin
15m / 1h
@enum Trend Down = 0 Up = 1
function handler(s, ai, ats, date)
ohlcv = ai.data[tf"1h"]
idx = dateindex(ohlcv, ats)
idx < 1 && return nothing
this_trend = ifelse(ohlcv[idx, :ema15] > ohlcv[idx, :ema40], Down, Up)
this_rsi = ai.data[tf"15m"][ats, :rsi]
if this_trend == Up && this_rsi < 40
price = closeat(ohlcv, ats)
amount = freecash(s) / price
call!(s, ai, MarketOrder{Buy}; date, amount)
elseif this_trend == Down && this_rsi > 60
price = closeat(ohlcv, ats)
if !isdust(ai, price)
call!(s, ai, CancelOrders())
call!(s, ai, MarketOrder{Sell}; date, amount=float(ai))
end
end
endBollingerBands
Mean reversion with 20-period Bollinger Bands
Phemex
Isolated
1m
function handler(s, ai, ats, ts)
call!(bbands!, s, ai, UpdateData(); cols=(:bb_lower, :bb_upper))
ohlcv = ai.data[s.timeframe]
lower = ohlcv[ats, :bb_lower]
upper = ohlcv[ats, :bb_upper]
current_price = closeat(ohlcv, ats)
balance_quoted = s.self.freecash(s) # free, not in pending orders
buy_value = float(balance_quoted) * 0.80
has_position = isopen(ai, Long())
if current_price < lower && !has_position
amount = buy_value / current_price
call!(s, ai, MarketOrder{Buy}; date=ts, amount)
elseif current_price > upper && has_position
call!(s, ai, Long(), ts, PositionClose())
end
endMarginStrat
QQE momentum trading on isolated margin
Binance
Isolated
1d
function handler(s, ai, ats, date)
call!(qqe!, s, ai, UpdateData(); cols=(:qqe,))
data = ohlcv(ai, tf"1d")
v = data[ats, :qqe]
trend = if v > 13.22
-1
elseif v < 8.96
1
else
0
end
pos = position(ai)
exposure = pos === nothing ? 0.0 : cash(pos)
@assert iszero(exposure) ||
islong(pos) && exposure >= 0.0 ||
isshort(pos) && exposure <= 0.0
# ... open / close positions from the trend signal
end