Strategy interface

Setup a new strategy

The simplest way to create a strategy is to use the interactive generator which will prompt for the required set of options to set.

julia> using Planar
julia> Planar.generate_strategy()
Strategy name: : MyNewStrategy

Timeframe:
   1m
 > 5m
   15m
   1h
   1d

Select exchange by:
 > volume
   markets
   nokyc

 > binance
   bitforex
   okx
   xt
   coinbase

Quote currency:
   USDT
   USDC
 > BTC
   ETH
   DOGE

Margin mode:
 > NoMargin
   Isolated

Activate strategy project at /run/media/fra/stateful-1/dev/Planar.jl/user/strategies/MyNewStrategy? [y]/n: y

Add project dependencies (comma separated): Indicators
   Resolving package versions...
   [...]
  Activating project at `/run/media/fra/stateful-1/dev/Planar.jl/user/strategies/MyNewStrategy`

┌ Info: New Strategy
│   name = "MyNewStrategy"
│   exchange = :binance
└   timeframe = "5m"
[ Info: Config file updated

Load strategy? [y]/n: 

julia> s = ans

Alternatively you can directly pass kwargs and skip interaction by passing ask=false.

Planar.generate_strat("MyNewStrategy", ask=false, exchange=:myexc)

or just use a config:

cfg = Planar.Config(exchange=:myexc)
Planar.generate_strat("MyNewStrategy", cfg)

Load a strategy

The strategy is instantiated by loading a julia module at runtime.

using Planar
cfg = Config(exchange=:kucoin) # Constructs a configuration object, choosing kucoin as exchange
s = strategy(:Example, cfg) # Load the Example strategy

The key is the name of the module (in this case Example) which will be imported from the included file "cfg/strategies/Example.jl" or "cfg/strategies/Example/src/Example.jl".

After the strategy module is imported the strategy is instantiated by calling the call!(::Type{S}, ::LoadStrategy, cfg) function.

> typeof(s)
Engine.Strategies.Strategy37{:Example, ExchangeTypes.ExchangeID{:kucoin}(), :USDT}

See here how the load method is defined.

module Example
using Planar

const DESCRIPTION = "Example"
const EXC = :phemex
const MARGIN = NoMargin
const TF = tf"1m"

@strategyenv!

function call!(::Type{<:SC}, ::LoadStrategy, config)
    assets = marketsid(S)
    s = Strategy(Example, assets; config)
    s
end

end

See that the load method dispatches on the strategy type with cfg as argument of type Misc.Config.

As a rule of thumb if the method should be called before the strategy is constructed, then it dispatches to the strategy type (Type{<:S}), otherwise the strategy instance (S). For convention the module property S of your strategy module, declares the strategy type (const S = Strategy{name, exc, ...}) and SC defines the same strategy type where the exchange is still generic.

Manual setup

If you want to create a strategy manually you can either:

  • Copy the user/strategies/Template.jl to a new file in the same directory and customize it.
  • Generate a new project in user/strategies and customize Template.jl to be your project entry file. The strategy Project.toml is used to store strategy config options. See other strategies examples for what the keys that are required.

For more advanced setups you can also use Planar as a library, and construct the strategy object directly from your own module:

using Planar
using MyDownStreamModule
s = Planar.Engine.Strategies.strategy(MyDownStreamModule)

Strategy interface

Both call! and call! functions adhere to a convention for function signatures. The first argument is always either an instance of the subject or its type, followed by the arguments of the function, with the last non kw argument being the verb which describes the purpose of the function. KW arguments are optional and don't have any requirements. We can see below that Type{S} is the subject, config is an argument, and ::LoadStrategy is the _verb.

List of strategy call! functions

Misc.call!Function

Places a limit order and synchronizes the cash balance.

call!(
    s::Strategies.Strategy{Misc.Live, N, <:ExchangeID, NoMargin, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.LimitOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    price,
    waitfor,
    synced,
    skipchecks,
    kwargs...
)

This function initiates a limit order through the _live_limit_order function. Once the order is placed, it synchronizes the cash balance in the live strategy to reflect the transaction. It returns the trade information once the transaction is complete.

Places a market order and synchronizes the cash balance.

call!(
    s::Strategies.Strategy{Misc.Live, N, <:ExchangeID, NoMargin, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.MarketOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    waitfor,
    synced,
    skipchecks,
    kwargs...
)

This function initiates a market order through the _live_market_order function. Once the order is placed, it synchronizes the cash balance in the live strategy to reflect the transaction. It returns the trade information once the transaction is complete.

Cancels all live orders of a certain type and synchronizes the cash balance.

call!(
    s::Strategies.Strategy{Misc.Live},
    ai::Instances.AssetInstance,
    ::Executors.CancelOrders;
    t,
    waitfor,
    confirm,
    synced,
    ids
) -> Bool

This function cancels all live orders of a certain side (buy/sell) through the live_cancel function. Once the orders are canceled, it waits for confirmation of the cancelation and then synchronizes the cash balance in the live strategy to reflect the cancelations. It returns a boolean indicating whether the cancellation was successful.

Updates leverage or places an order in a live trading strategy.

call!(
    s::Strategies.Strategy{Misc.Live, N, <:ExchangeID, <:Misc.WithMargin, C} where {N, C},
    ai::Instances.AssetInstance{<:AbstractAsset, <:ExchangeID, M} where M<:Misc.WithMargin,
    lev,
    ::Executors.UpdateLeverage;
    pos,
    synced,
    atol,
    force
)

This function either updates the leverage of a position or places an order in a live trading strategy. It first checks if the position is open or has pending orders. If not, it updates the leverage on the exchange and then synchronizes the position. If an order is to be placed, it checks for any open positions on the opposite side and places the order if none exist. The function returns the trade or leverage update status.

Executes a limit order in a live trading strategy.

call!(
    s::Strategies.Strategy{Misc.Live, N, <:ExchangeID, Isolated, C} where {N, C},
    ai::Instances.AssetInstance{<:AbstractAsset, <:ExchangeID, M} where M<:Misc.WithMargin,
    t::Type{<:OrderTypes.Order{<:OrderTypes.LimitOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    price,
    waitfor,
    skipchecks,
    synced,
    kwargs...
)

This function executes a limit order in a live trading strategy, given a strategy s, an asset instance ai, and a trade type t. It checks for open positions on the opposite side and places the order if none exist. The function returns the trade or leverage update status.

Executes a market order in a live trading strategy.

call!(
    s::Strategies.Strategy{Misc.Live, N, <:ExchangeID, Isolated, C} where {N, C},
    ai::Instances.AssetInstance{<:AbstractAsset, <:ExchangeID, M} where M<:Misc.WithMargin,
    t::Type{<:OrderTypes.Order{<:OrderTypes.MarketOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    waitfor,
    skipchecks,
    synced,
    kwargs...
)

This function executes a market order in a live trading strategy, given a strategy s, an asset instance ai, and a trade type t. It checks for open positions on the opposite side and places the order if none exist. The function returns the trade or leverage update status.

Closes a leveraged position in a live trading strategy.

call!(
    s::Strategies.Strategy{Misc.Live, N, <:ExchangeID, <:Misc.WithMargin, C} where {N, C},
    ai::Instances.AssetInstance{<:AbstractAsset, <:ExchangeID, M} where M<:Misc.WithMargin,
    ::Union{Type{P<:Misc.PositionSide}, Type{O} where O<:(OrderTypes.Order{<:OrderTypes.OrderType, <:AbstractAsset, <:ExchangeID, P<:Misc.PositionSide}), Type{T} where T<:(OrderTypes.Trade{<:OrderTypes.OrderType, <:AbstractAsset, <:ExchangeID, P<:Misc.PositionSide}), OrderTypes.Order{<:OrderTypes.OrderType, <:AbstractAsset, <:ExchangeID, P<:Misc.PositionSide}, OrderTypes.Trade{<:OrderTypes.OrderType, <:AbstractAsset, <:ExchangeID, P<:Misc.PositionSide}, P<:Misc.PositionSide},
    date,
    ::Instances.PositionClose;
    t,
    waitfor,
    kwargs...
) -> Bool

This function cancels any pending orders and checks the position status. If the position is open, it places a closing trade and waits for it to be executed. The function returns true if the position is successfully closed, false otherwise.

Executes the OHLCV watcher for a real-time strategy.

call!(
    s::Strategies.RTStrategy,
    ::Executors.WatchOHLCV,
    args...;
    kwargs...
)

This function triggers the execution of the OHLCV (Open, High, Low, Close, Volume) watcher for a real-time strategy s.

Triggers the data update for a real-time strategy.

call!(
    f::Function,
    s::Strategies.RTStrategy,
    ::Executors.UpdateData;
    cols,
    timeframe
)

This function initiates the update of data for a real-time strategy s. The update is performed for the specified columns cols and uses the provided timeframe timeframe.

Triggers the data update for an asset instance in a real-time strategy.

call!(
    f::Function,
    s::Strategies.RTStrategy,
    ai::Instances.AssetInstance,
    ::Executors.UpdateData;
    cols,
    timeframe
)

This function triggers the update of data for a specific asset instance ai in a real-time strategy s. The update is performed for the specified columns cols and uses the provided timeframe timeframe.

Initializes the data for a real-time strategy.

call!(
    f::Function,
    s::Strategies.RTStrategy,
    ::Executors.InitData;
    cols,
    timeframe
)

This function initializes the data for a real-time strategy s. The initialization is performed for the specified columns cols and uses the provided timeframe timeframe. After the initialization, the updated_at! function is called to update the timestamp for the updated columns.

Called on each timestep iteration, possible multiple times. Receives:

  • current_time: the current timestamp to evaluate (the current candle would be current_time - timeframe).
  • ctx: The context of the executor.
call!(
    _::Strategies.Strategy,
    current_time::Dates.DateTime,
    ctx
)

Called to construct the strategy, should return the strategy instance.

call!(
    _::Type{<:Strategies.Strategy},
    cfg,
    _::Strategies.LoadStrategy
)

Called at the end of the reset! function applied to a strategy.

call!(_::Strategies.Strategy, _::Strategies.ResetStrategy)

How much lookback data the strategy needs.

call!(
    s::Strategies.Strategy,
    _::Strategies.WarmupPeriod
) -> Any

When an order is canceled the strategy is pinged with an order error.

call!(
    s::Strategies.Strategy,
    ::OrderTypes.Order,
    err::OrderTypes.OrderError,
    ::Instances.AssetInstance;
    kwargs...
) -> Any

Market symbols that populate the strategy universe

Called before the strategy is started.

call!(_::Strategies.Strategy, _::Strategies.StartStrategy)

Called after the strategy is stopped.

call!(_::Strategies.Strategy, _::Strategies.StopStrategy)

Creates a simulated limit order.

call!(
    s::Strategies.SimStrategy{N, <:ExchangeID, NoMargin, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.LimitOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    kwargs...
)

The function call! is responsible for creating a simulated limit order. It creates the order using create_sim_limit_order, checks if the order is not nothing, and then calls limitorder_ifprice!. The parameters include a strategy s, an asset ai, and a type t. The function also accepts an amount and additional arguments kwargs....

Creates a simulated market order.

call!(
    s::Strategies.SimStrategy{N, <:ExchangeID, NoMargin, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.MarketOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    date,
    kwargs...
)

The function call! creates a simulated market order using create_sim_market_order. It checks if the order is not nothing, and then calls marketorder!. Parameters include a strategy s, an asset ai, a type t, an amount and a date. Additional arguments can be passed through kwargs....

Cancel orders for a specific asset instance.

call!(
    s::Strategies.Strategy{<:Union{Misc.Paper, Misc.Sim}},
    ai::Instances.AssetInstance,
    ::Executors.CancelOrders;
    t,
    kwargs...
) -> Bool

The function call! cancels all orders for a specific asset instance ai. It iterates over the orders of the asset and cancels each one using cancel!. Parameters include a strategy s, an asset instance ai, and a type t which defaults to BuyOrSell. Additional arguments can be passed through kwargs....

After a position was updated from a trade.

call!(
    _::Strategies.Strategy{X, N, <:ExchangeID, <:Misc.WithMargin, C} where {X<:Misc.ExecMode, N, C},
    ai,
    trade::OrderTypes.Trade,
    _::Instances.Position,
    _::Instances.PositionChange
) -> Bool

This function is called after a position is updated due to a trade. It takes in a MarginStrategy, ai, trade, Position, and PositionChange as arguments. The function does not return any value.

After a position update from a candle.

call!(
    _::Strategies.Strategy{X, N, <:ExchangeID, <:Misc.WithMargin, C} where {X<:Misc.ExecMode, N, C},
    ai,
    date::Dates.DateTime,
    _::Instances.Position,
    _::Instances.PositionUpdate
)

This function is called after a position is updated from a candle. It provides the necessary functionality for handling position updates in response to candle data.

Creates a simulated limit order, updating a levarged position.

"Creates a simulated market order, updating a levarged position.

Protections

Usually an exchange checks before executing a trade if right after the trade the position would be liquidated, and would prevent you to do such trade, however we always check after the trade, and liquidate accordingly, this is pessimistic since we can't ensure that all exchanges have such protections in place.

Closes a leveraged position.

Closes all strategy positions

Update position leverage. Returns true if the update was successful, false otherwise.

The leverage is not updated when the position has pending orders or is open (and it will return false in such cases.)

Watchers are not used in SimMode.

Data should be pre initialized in SimMode.

Data should be pre initialized in SimMode.

Data should be pre initialized in SimMode.

Initialize data for each asset in the strategy.

call!(
    f::Function,
    s::Strategies.SimStrategy,
    ::Executors.InitData;
    cols,
    timeframe
)

This function initializes data for each asset in the strategy by retrieving the OHLCV data and setting the specified columns.

Creates a paper market order.

call!(
    s::Strategies.Strategy{Misc.Paper, N, <:ExchangeID, NoMargin, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.MarketOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    date,
    price,
    kwargs...
)

The function creates a paper market order for a given strategy and asset. It specifies the amount of the order and the type of order (e.g., limit order, immediate order).

Creates a simulated limit order.

call!(
    s::Strategies.Strategy{Misc.Paper, N, <:ExchangeID, NoMargin, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.LimitOrderType}};
    amount,
    date,
    kwargs...
)

The function creates a simulated limit order for a given strategy and asset. It specifies the amount of the order and the date. Additional keyword arguments can be passed.

Creates a paper market order, updating a leveraged position.

call!(
    s::Strategies.Strategy{Misc.Paper, N, <:ExchangeID, Isolated, C} where {N, C},
    ai::Instances.AssetInstance{<:AbstractAsset, <:ExchangeID, M} where M<:Misc.WithMargin,
    t::Type{<:OrderTypes.Order{<:OrderTypes.MarketOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    date,
    price,
    kwargs...
)

The function creates a paper market order for a given strategy, asset, and order type. It specifies the amount and date of the order. Additional keyword arguments can be passed.

Creates a simulated limit order.

call!(
    s::Strategies.Strategy{Misc.Paper, N, <:ExchangeID, Isolated, C} where {N, C},
    ai,
    t::Type{<:OrderTypes.Order{<:OrderTypes.LimitOrderType{S}, <:AbstractAsset, <:ExchangeID, P} where {S<:OrderTypes.OrderSide, P<:Misc.PositionSide}};
    amount,
    date,
    kwargs...
)

The function creates a simulated limit order for a given strategy, asset, and order type. It specifies the amount and date of the order. Additional keyword arguments can be passed.

Closes positions for a live margin strategy.

call!(
    s::Strategies.RTStrategy{var"#s232", N, <:ExchangeID, <:Misc.WithMargin, C} where {var"#s232"<:Union{Misc.Live, Misc.Paper}, N, C},
    bp::OrderTypes.ByPos,
    date,
    ::Instances.PositionClose;
    kwargs...
)

Initiates asynchronous position closing for each asset instance in the strategy's universe.

Returns Optimizations.ContextSpace for backtesting

call!(_::Strategies.Strategy, _::Executors.OptSetup)

The ctx field (Executors.Context) specifies the backtest time period, while space is either an already built BlackBoxOptim.SearchSpace subtype or a tuple (Symbol, args...) for a pre-defined BBO package search space.

Applies parameters to strategy before backtest

call!(_::Strategies.Strategy, params, _::Executors.OptRun)

Initializes warmup attributes for a strategy.

call!(
    s::Strategies.Strategy,
    ::InitSimWarmup;
    timeout,
    n_candles
) -> Bool

Initiates the warmup process for a real-time strategy instance.

call!(
    cb::Function,
    s::Strategies.RTStrategy,
    ai::Instances.AssetInstance,
    ats::Dates.DateTime,
    ::SimWarmup;
    n_candles
)

If warmup has not been previously completed for the given asset instance, it performs the necessary preparations.

Removing a strategy

The function remove_strategy allows to discard a strategy by its name. It will delete the julia file or the project directory and optionally the config entry.

julia> Planar.remove_strategy("MyNewStrategy")
Really delete strategy located at /run/media/fra/stateful-1/dev/Planar.jl/user/strategies/MyNewStrategy? [n]/y: y
[ Info: Strategy removed
Remove user config entry MyNewStrategy? [n]/y: y

Strategy examples

Strategy examples can be found in the user/strategies folder, some strategies are single files like Example.jl while strategies like BollingerBands or ExampleMargin are project based.

Resizeable universe

The universe (s.universe) is backed by a DataFrame (s.universe.data). It is possible to add and remove assets from the universe during runtime, (although not extensively tested).