CryptoCurrency / Docker · June 2, 2021

FreqTrade deploy, understanding the Order Book

In this episode, we’re deploying FreqTrade, an open-source CryptoCurrency trading bot, wrapping it up in a nice multi-tabbed webUI called Muximux, and managing it all with Traefik web proxy with real Let’s Encrypt auto-renewing SSL certificates! I’ll also take you through the config basics, what a simple strategy file looks like, and how crypto exchange order books work. Check it out! After the video, I’ve got your example code below!

YouTube player

Let’s start with a basic docker-compose.yml for your FreqTrade instance:

version: '3.6'

networks:
  proxy:
    external: true

services:
  freqtrade:
    image: freqtradeorg/freqtrade:develop
    networks:
      - proxy
    volumes:
      - "./user_data:/freqtrade/user_data"
      - /etc/timezone:/etc/timezone:ro
    # Default command used when running `docker compose up`
    command: >
      trade
      --logfile /freqtrade/user_data/logs/freqtrade.log
      --config /freqtrade/user_data/config.json
      --strategy BinHV45
      --db-url sqlite:///user_data/tradesv3.sqlite
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      restart_policy:
        condition: on-failure
        delay: 5s
      labels:
         - 'traefik.enable=true'
         - 'traefik.http.routers.binhv45.tls=true'
         - 'traefik.http.routers.binhv45.rule=Host(`binhv45.dmz.yourdomain.com`)'
         - 'traefik.http.services.binhv45.loadbalancer.server.port=8080'

If you don’t already have a Traefik proxy set up, you’ll want to refer to my video and example code for that. It’ll make your life so much easier!

Next, let’s take a look as a sample config.json file for FreqTrade. This will vary based on your config, but this is a good starting point:

{
    "max_open_trades": 25,
    "stake_currency": "USDT",
    "stake_amount": 50,
    "tradable_balance_ratio": 0.99,
    "fiat_display_currency": "USD",
    "timeframe": "1m",
    "dry_run": true,
    "cancel_open_orders_on_exit": false,
    "unfilledtimeout": {
        "buy": 10,
        "sell": 30
    },
    "bid_strategy": {
        "price_side": "bid",
        "ask_last_balance": 0.0,
        "use_order_book": false,
        "order_book_top": 1,
        "check_depth_of_market": {
            "enabled": false,
            "bids_to_ask_delta": 1
        }
    },
    "ask_strategy": {
        "price_side": "ask",
        "use_order_book": false,
        "order_book_min": 1,
        "order_book_max": 1,
        "use_sell_signal": true,
        "ignore_roi_if_buy_signal": true
    },
    "download_trades": true,
    "exchange": {
        "name": "binanceus",
        "key": "",
        "secret": "",
        "ccxt_config": {"enableRateLimit": true},
        "ccxt_async_config": {
            "enableRateLimit": true,
            "rateLimit": 200
        },
        "pair_whitelist": [
          "BTC/USDT",
          "ETH/USDT",
          "ETC/USDT",
          "LTC/USDT",
          "XLM/USDT",
          "ADA/USDT"
        ],
        "pair_blacklist": [
          "XRP/USD",
          "USDT/USD",
          "USDC/USD",
          "EUR/USD"
        ]
    },
    "pairlists": [
      {
//          "method": "StaticPairList"}
          "method": "VolumePairList",
          "number_assets": 50,
          "sort_key": "quoteVolume",
          "refresh_period": 1800
      },
      {"method": "AgeFilter", "min_days_listed": 10},
      {
        "method": "RangeStabilityFilter",
        "lookback_days": 5,
        "min_rate_of_change": 0.01,
        "refresh_period": 1440
      }
     ],

    "edge": {
        "enabled": false,
        "process_throttle_secs": 3600,
        "calculate_since_number_of_days": 7,
        "allowed_risk": 0.01,
        "minimum_winrate": 0.60,
        "minimum_expectancy": 0.20,
        "min_trade_number": 10,
        "max_trade_duration_minute": 1440,
        "remove_pumps": false
    },
    "telegram": {
        "enabled": false,
        "token": "",
        "chat_id": ""
    },
    "api_server": {
        "enabled": true,
        "enable_openapi": true,
        "listen_ip_address": "0.0.0.0",
        "listen_port": 8080,
        "verbosity": "info",
        "jwt_secret_key": "somethingrandom",
        "CORS_origins": [],
        "username": "api",
        "password": "api"
    },
    "initial_state": "running",
    "forcebuy_enable": false,
    "internals": {
        "process_throttle_secs": 5
    }
}

Next, you’ll need a strategy file. This is the example we’re using in the video, but I also recommend you check out the examples on FreqTrade’s own GitHub page. The below is BinHV45.py stored in /user_data/strategies/ folder:

# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame
import numpy as np
# --------------------------------

import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib


def bollinger_bands(stock_price, window_size, num_of_std):
    rolling_mean = stock_price.rolling(window=window_size).mean()
    rolling_std = stock_price.rolling(window=window_size).std()
    lower_band = rolling_mean - (rolling_std * num_of_std)

    return rolling_mean, lower_band


class BinHV45(IStrategy):
    minimal_roi = {
    #    "0": 0.0125
      "0": 0.99
    }

    stoploss = -0.05
    timeframe = '1m'
    trailing_stop = True
    trailing_only_offset_is_reached = True
    trailing_stop_positive_offset = 0.00375  # Trigger positive stoploss once crosses above this percentage
    trailing_stop_positive = 0.00175 # Sell asset if it dips down this much


    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        mid, lower = bollinger_bands(dataframe['close'], window_size=40, num_of_std=2)
        dataframe['mid'] = np.nan_to_num(mid)
        dataframe['lower'] = np.nan_to_num(lower)
        dataframe['bbdelta'] = (dataframe['mid'] - dataframe['lower']).abs()
        dataframe['pricedelta'] = (dataframe['open'] - dataframe['close']).abs()
        dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs()
        dataframe['tail'] = (dataframe['close'] - dataframe['low']).abs()
        return dataframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                dataframe['lower'].shift().gt(0) &
                dataframe['bbdelta'].gt(dataframe['close'] * 0.008) &
                dataframe['closedelta'].gt(dataframe['close'] * 0.0175) &
                dataframe['tail'].lt(dataframe['bbdelta'] * 0.25) &
                dataframe['close'].lt(dataframe['lower'].shift()) &
                dataframe['close'].le(dataframe['close'].shift())
            ),
            'buy'] = 1
        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        no sell signal
        """
        dataframe.loc[:, 'sell'] = 0
        return dataframe