Bitfinex, , has more than 180 open-source repositories in GitHub–from a to . The most sophisticated trading toolkit developed by Bitfinex, though, is Honey Framework with many useful packages such as , , , and . one of the largest cryptocurrency digital exchanges Tezos JS library UI goodies bfx-hf-ui bfx-hf-strategy bfx-hf-indicators bfx-hf-strategy-exec In this tutorial, we'll use the Honey Framework ecosystem to define and backtest a trend trading strategy on historic data for the past 500 days. Requirements Let's install the libraries we're going to use. First, run the following command to install Honey Framework libraries. npm install bfx-hf-strategy bfx-api- bfx-hf-util bfx-hf-indicators node -models We'll also need the following packages to display the trades on the command line. npm install cli- colors table Eventually, install library as well to retrieve the data from Bitfinex API. node-fetch npm install node -fetch Strategy We use a simple trend trading strategy based on the following logic: position if the price is between -1 SD and -2 SD Bollinger Bands® and short SMA is below the long SMA. Open short position if the candle is green and more than 75% of its body crossed above the -1 SD band. Close short position if the price is between +1 SD and +2 SD bands and the short simple moving average is above the long simple moving average. Open long position if the candle is red and more than 75% of its body crossed below +1 SD band. Close long The logic behind the strategy is fairly straightforward: if the price is between -1 SD and +1 SD bands (No Man's Land), we close our position. If the price is in the sell-zone (between the lower bands) and the market trend is bearish (short SMA is below long SMA), we open a short position. On the other hand, if the price is between upper bands and the market is bullish, we open a long position (in other words, we only trade in direction of the overall trend). Check to find more information about trend trading based on Bollinger Bands®. this article from Investopedia Code Let's get our hands dirty. First, create that defines a dummy strategy. strategy.js { SYMBOLS, TIME_FRAMES } = ( ); { BollingerBands, SMA } = ( ); HFS = ( ); .exports = HFS.define({ : , : , symbol, tf, : { : BollingerBands([ , ]), : BollingerBands([ , ]), : SMA([ ]), : SMA([ ]), }, }); // strategy.js const require "bfx-hf-util" const require "bfx-hf-indicators" const require "bfx-hf-strategy" module ( ) => { symbol = SYMBOLS.BTC_USD, tf = TIME_FRAMES.ONE_DAY } id "bbands" name "bbands" indicators bbands2 new 20 2 bbands1 new 20 1 smaShort new 25 smaLong new 100 Let's break it down. We define the default symbol (BTCUSD) and timeframe (one day). Then, we define the indicators we're going to use. We need two Bollinger Band® bands, both in the period of 20 days. We give a multiplier of 2 to the first (±2 standard deviations) and a multiplier of 1 to the second one (±1 standard deviations). In the end, we add two SMAs, one in a 25-day period (short SMA) and the other in a 100-day period (long SMA). to find a list of all indicators available in Bitfinex Honey Framework. Check this We can enter the market now. The strategy we defined accepts five handler methods to update positions and make trades on each event: - called when no position is open onEnter - called when a long position is open onUpdateLong - called when a short position is open onUpdateShort - called when any position is open onUpdate - called on every tick onPriceUpdate Event handlers receive two parameters, (the strategy context) and (point-in-time data), they look for trade opportunities and update if there's a trade to be made. state update state Enter Into the Market Let's define the strategy to enter the market. First, add to encapsulate the reusable logic. Define in it to extract Bollinger Band® bands values from the state. helper.js getBBands HFS = ( ); getBBands = { indicatorValues = HFS.indicatorValues(state); { bbands1, bbands2 } = indicatorValues; { : plusOne, : minusOne } = bbands1; { : plusTwo, : minusTwo } = bbands2; { plusOne, plusTwo, minusOne, minusTwo, }; }; .exports = { getBBands } // helpers.js const require "bfx-hf-strategy" const ( ) => state const const const top bottom const top bottom return module returns a map of all indicators defined in the strategy. We then extract the Bollinger Bands objects from them, which include three values: top, bottom, and middle. Eventually, the method returns ±1 and ±2 SD bands. indicatorValues Define another method to extract uptrend/downtrend data from the state. checkIfIsUptrend = { indicatorValues = HFS.indicatorValues(state); { smaShort, smaLong } = indicatorValues; isUpTrend = smaShort > smaLong; isUpTrend; }; .exports = { getBBands, checkIfIsUptrend, } // helpers.js /* getBBands here */ const ( ) => state const const const return module The method simply checks if short SMA is above long SMA and infers the trend from it. Add . We'll use it to find trade opportunities. look_for_trade.js HFS = ( ); { getBBands, checkIfIsUptrend } = ( ); .exports = (state = {}, update = {}) => { { price, : timestamp } = update; orderParams = { : timestamp, : , price, }; bands = getBBands(state); isBuyZone = bands.plusTwo >= price && price >= bands.plusOne; isSellZone = bands.minusTwo <= price && price <= bands.minusOne; isUptrend = checkIfIsUptrend(state); (isSellZone && !isUptrend) { HFS.openShortPositionMarket(state, orderParams); } (isBuyZone && isUptrend) { HFS.openLongPositionMarket(state, orderParams); } state; }; // look_for_trade.js const require "bfx-hf-strategy" const require "./helpers" module async const mts const mtsCreate amount 1 const // between +1 SD and +2 SD from mean, the trend is up const // between -1 SD and -2 SD from mean, the trend is down const const if return if return return The method checks if the price is in the trade zone (between +1 and +2 SD or -1 SD and -2 SD bands). If the price is in the sell zone and the trend is bullish, it opens a short position, and if the price is in the sell zone and the trend is bearish, it opens a long position. If none is true, it simply returns the previous state. Notice that we use Honey Framework helper functions to open positions. You can find the list of all helper functions . Add to use the function we just defined to handle onEnter events. here on_enter.js lookForTrade = ( ); .exports = (state = {}, update = {}) => { newState = lookForTrade(state, update); newState; }; // on_enter.js const require "./look_for_trade" module async await return Now add the handler to the strategy. on_enter = ( ); .exports = HFS.define({ onEnter: on_enter, }); // strategy.js /* other imports */ // handlers const require "./on_enter" module ( ) => { symbol = SYMBOLS.BTC_USD, tf = TIME_FRAMES.ONE_DAY } /* other strategy properties */ Update Positions Well done! We've just exposed ourselves to the market. But we should add other handlers to react to market updates while we have an open position. Define another method in to check if 75% or more of the candle body crossed the inner bands. helpers.js HFS = ( ); checkIfShouldClose = { candleBody = .abs(close - open); closeToNoMansLandDist = position == ? close - bands.minusOne : bands.plusOne - close; shouldClose = closeToNoMansLandDist / candleBody >= ; shouldClose; }; .exports = { getBBands, checkIfIsUptrend, checkIfShouldClose, }; // helpers.js const require "bfx-hf-strategy" /* Other methods */ const ( ) => close, open, bands, position const Math const "short" // check if 75% of candle body entered to the No Man's Land. // see: https://www.investopedia.com/thmb/AtmrjGx96Tbz8f1cfnGOGzpqHqU=/2378x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/dotdash_Final_Using_Bollinger_Bands_to_Gauge_Trends_Oct_2020-02-f76c639116734ccfb8493dce32ed149a.jpg const 0.75 return module calculates the candle body height by finding the distance between open and close values. Then, it checks how much of the candle entered the No Man's Land. If more than 75% of the candle body is in between two inner bands, it closes the position. checkIfShouldClose It's time to create . on_long.js HFS = ( ); lookForTrade = ( ); { getBBands, checkIfShouldClose } = ( ); .exports = (state = {}, update = {}) => { { candle, : timestamp, price } = update; { open, close } = candle; orderParams = { : timestamp, : , price, }; bands = getBBands(state); isCandleGreen = open <= close; (isCandleGreen) state; isCloseAbove1Std = bands.plusOne < close; (isCloseAbove1Std) state; shouldClose = checkIfShouldClose(close, open, bands, ); (!shouldClose) state; newState = HFS.closePositionMarket(state, orderParams); newState = lookForTrade(newState, update); newState; }; // on_long.js const require "bfx-hf-strategy" const require "./look_for_trade" const require "./helpers" module async const mts const const mtsCreate amount 1 const const if return const if return // check if 75% of candle body is below 1 std const "long" if return let await // look if should open short await return The method we just defined checks if: The candle is green. The candle is above the No Man's Land (i.e., the closing value is above the +1 SD band). Less than 75% of the candle is in the No Man's Land. And if any of these are correct, it doesn't touch the position. Otherwise, it closes the long position using helper method and then calls to check if a short position should be opened. Now, let's define our strategy while shorting. closePositionMarket lookForTrade HFS = ( ); lookForTrade = ( ); { getBBands, checkIfShouldClose } = ( ); .exports = (state = {}, update = {}) => { { candle, : timestamp, price } = update; { open, close } = candle; orderParams = { : timestamp, : , price, }; bands = getBBands(state); isCandleRed = open >= close; (isCandleRed) state; isCloseBelowStd = bands.minusOne > close; (isCloseBelowStd) state; shouldClose = checkIfShouldClose(close, open, bands, ); (!shouldClose) state; newState = HFS.closePositionMarket(state, orderParams); newState = lookForTrade(newState, update); newState; }; // on_short.js const require "bfx-hf-strategy" const require "./look_for_trade" const require "./helpers" module async const mts const const mtsCreate amount 1 const const if return const if return // check if 75% of candle body is below 1 std const "short" if return let await // look if should open long await return checks if: on_short.js The candle is red. The candle is below the No Man's Land (i.e., the closing value is below the -1 SD band). Less than 75% of the candle is in the No Man's Land. And if all above are false, it closes the short position and looks for new trade opportunities. That's it. We've finished defining the strategy. Add the new event handlers to . Then we can start testing the algorithm. strategy.js on_enter = ( ); on_long = ( ); on_short = ( ); .exports = HFS.define({ onEnter: on_enter, : on_long, : on_short, }); // strategy.js /* other imports */ // handlers const require "./on_enter" const require "./on_long" const require "./on_short" module ( ) => { symbol = SYMBOLS.BTC_USD, tf = TIME_FRAMES.ONE_DAY } /* Other strategy properties */ onUpdateLong onUpdateShort Test on Historical Data Add to retrieve historic data from Bitfinex API. get_data.js ; fetch = ( ); url = ; get_500_days = { date = (); end = date.getTime(); date.setDate(date.getDate() - ); start = date.getTime(); { start, end }; }; .exports = { { { start, end } = get_500_days(); pathParams = ; queryParams = ; req = fetch( ); response = req.json(); response; } (err) { .log(err); } }; // get_data.js "use strict" const require "node-fetch" const "https://api-pub.bitfinex.com/v2/" const => () let new Date const 500 const return module async ( ) function getData { symbol, tf } try const const `candles/trade: : /hist` ${tf} ${symbol} const `start= &end= &limit=500` ${start} ${end} const await ` / ? ` ${url} ${pathParams} ${queryParams} const await return catch console fetches OHLC data of the past 500 days, given a symbol and a timeframe. You can modify the parameters to meet your needs. Find more information about Bitfinex API's public endpoint for candle data . getData here Add to define the execution logic. exec.js process.env.DEBUG = ; { Candle } = ( ); { SYMBOLS, TIME_FRAMES } = ( ); logTrades = ( ); HFS = ( ); getData = ( ); BBandsStrategy = ( ); market = { : SYMBOLS.BTC_USD, : TIME_FRAMES.ONE_DAY, }; getCandles = () => { rawCandleData = getData(market); candles = rawCandleData .sort( a[ ] - b[ ]) .map( ({ ...new Candle(candle).toJS(), ...market, })); candles; }; run = () => { candles = getCandles(); strategyState = BBandsStrategy(market); ( i = ; i < candles.length; i += ) { strategyState = HFS.onCandle(strategyState, candles[i]); } logTrades(strategyState); }; { run(); } (e) { .error(e); } // exec.js "*" const require "bfx-api-node-models" const require "bfx-hf-util" const require "bfx-hf-strategy/lib/debug/log_trades" const require "bfx-hf-strategy" const require "./get_data" const require "./strategy" const symbol tf const async const await // attach market data const ( ) => a, b 0 0 ( ) => candle return const async const await let for let 0 1 await try catch console method retrieves the data and attaches market information to them. During live execution, we may receive data from multiple sources for different symbols and different timeframes, thus, it's necessary to attach this information to raw data. getCandles Then, gets candle data and the strategy and passes them to helper method that orchestrates relevant strategy event handlers for the new price action data. Eventually, logs the results using function. run onCandle run logTrades Let's test the algorithm. Open the command line in the project's directory and run the following command. node exec The result will be something like this: You can find a list of all positions along with the amount, price, fee, and profit/loss figure of each one. At the bottom, general information about the strategy is logged. The bot has made 37 trades, opened 19 positions, and gained 3960.91$. You can modify and change how it displays the results. helper function log_trades.js That's it! We've defined and backtested a simple trading strategy using Bitfinex Honey Framework toolkit. The example pieces of code in the article are available . here