Streategy
This page describes how to create a custom strategy in Ninjabot
Categories:
Strategy Functions
To create a custom strategy, you need to create a Struct
that implements the following methods:
type Strategy interface {
Timeframe() string
WarmupPeriod() int
Indicators(dataframe *model.Dataframe) []ChartIndicator
OnCandle(dataframe *model.Dataframe, broker service.Broker)
OnPartialCandle(df *model.Dataframe, broker service.Broker) // Optional
}
Timeframe
: specifies the strategy timeframe, eg: “15m”, “1h”, “1d”, “1w”.WarmupPeriod
: specifies the number of candles necessary to pre-load before the bot start. For example, if you use a 9-period moving average strategy, theWarmupPeriod
should be 9.Indicators
: this function creates custom indicators, it is called for each new candle received. You can also return a list of indicators to display in the chart.OnCandle
: this function is also called for each new closed candle, afterIndicators
execution. This function should contain your buy and sell rules.Dataframe
object contains indicators and indicators from candles. The buy and sell operations can be performed through theBroker
operator.OnPartialCandle
: this functions is optional, it will be called with high frequency, usually called every 2 seconds with partial data of current candle.
Example
The following code presents a strategy with a single indicator. We defined an Exponential Moving Average (EMA) of 9 periods. For each candle, we create a buy order when the price closes above the EMA, and sell when the price closes under the EMA.
import (
"github.com/rodrigo-brito/ninjabot"
"github.com/rodrigo-brito/ninjabot/service"
"github.com/markcheno/go-talib"
log "github.com/sirupsen/logrus"
)
type CrossEMA struct{}
func (e CrossEMA) Timeframe() string {
return "1d" // examples: 1m, 5m, 15m, 30m, 1h, 4h, 12h, 1d, 1w
}
func (e CrossEMA) WarmupPeriod() int {
return 9 // warmup period, to preload indicators
}
func (e CrossEMA) Indicators(df *ninjabot.Dataframe) []strategy.ChartIndicator {
// define a custom indicator, Exponential Moving Average of 9 periods
df.Metadata["ema9"] = talib.Ema(df.Close, 9)
// (Optional) you can return a list of indicators to include in the final chart
return []strategy.ChartIndicator{
{
Overlay: true,
Time: df.Time,
GroupName: "EMA",
Metrics: []strategy.IndicatorMetric{
{
Values: df.Metadata["ema9"],
Name: "EMA 9",
Color: "red",
Style: strategy.StyleLine,
},
},
},
}
}
func (e *CrossEMA) OnCandle(df *ninjabot.Dataframe, broker service.Broker) {
// Get the quote and assets information
assetPosition, quotePosition, err := broker.Position(df.Pair)
if err != nil {
log.Error(err)
}
// Check if we have more than 10 USDT available in the wallet and the buy signal is triggered
if quotePosition > 10 && df.Close.Crossover(df.Metadata["ema9"]) {
_, err := broker.CreateOrderMarketQuote(ninjabot.SideTypeBuy, df.Pair, quotePosition*0.99)
if err != nil {
log.Error(err)
}
}
// Check if we have position in the pair and the sell signal is triggered
if assetPosition > 0 &&
df.Close.Crossunder(df.Metadata["ema9"]) {
_, err := broker.CreateOrderMarket(ninjabot.SideTypeSell, df.Pair, assetPosition)
if err != nil {
log.Error(err)
}
}
}
Heikin Ashi candle type support
- CSV Feed exchange
csvFeed, err := exchange.NewCSVFeed( strategy.Timeframe(), exchange.PairFeed{ Pair: "FTMUSDT", File: "testdata/ftm-1d.csv", Timeframe: "1d", HeikinAshi: true, },
- Binance
binance, err := exchange.NewBinance(ctx, exchange.WithBinanceHeikinAshiCandle())
High Frequency Trading (HFT)
You also have access to partial candle updates through the function OnPartialCandle
. This can be useful for handling high frequency logic, such as using trailing stop or scalping techniques. See an example of usage below. This function is usually called every 2 seconds and may have small time variations.
func (e *CrossEMA) OnPartialCandle(df *model.Dataframe, broker service.Broker) {
// my logic here...
}