Skip to content

Latest

Creating a Custom Market Making Strategy - Part 3

Code: https://gist.github.com/cardosofede/d85a9d5ed5b7414728bcf967b540b9cb

Video:

In this exercise, let’s customize the output of the status command so that we can easily see what’s happening:

  • We will use the same file: quickstart_script_2.py
  • We’ll add a function that overrides the format_status method

Let's code

Let’s look at the default format_status method first, which is in the ScriptStrategyBase class that each script inherits from:

Default format_status

    def format_status(self) -> str:
    """
    Returns status of the current strategy on user balances and current active orders. This function is called
    when status command is issued. Override this function to create custom status display output.
    """
    if not self.ready_to_trade:
        return "Market connectors are not ready."
    lines = []
    warning_lines = []
    warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples()))

    balance_df = self.get_balance_df()
    lines.extend(["", "  Balances:"] + ["    " + line for line in balance_df.to_string(index=False).split("\n")])

    try:
        df = self.active_orders_df()
        lines.extend(["", "  Orders:"] + ["    " + line for line in df.to_string(index=False).split("\n")])
    except ValueError:
        lines.extend(["", "  No active maker orders."])

    warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples()))
    if len(warning_lines) > 0:
        lines.extend(["", "*** WARNINGS ***"] + warning_lines)
    return "\n".join(lines)
  • Note that the method returns a string; this string will be displayed when the users runs the status command.
  • If the markets are not ready to trade, the string will be “Market connectors are not ready”
  • There are two lists that we building:
    • lines: this list appends all the information that we want to show by using the method extend on the list
    • warning_lines: this list appends the network and balance warnings
  • We can transform a DataFrame to text using the to_string method
  • Lastly, to return the final string, the join method join all the strings that we have in the two lists, adding \n to inject a new line as a separator.
  • Note that when you ran the status --live command in the previous example, the output that you were seeing was the result of this (un-customized) method.

Now, let’s code our custom format_status method. Add the following code inside the QuickstartScript2 class:

Custom format_status

def format_status(self) -> str:
        if not self.ready_to_trade:
            return "Market connectors are not ready."
        mid_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.MidPrice)
        best_ask = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.BestAsk)
        best_bid = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.BestBid)
        last_trade_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.LastTrade)
        custom_format_status = f"""
| Mid price: {mid_price:.2f}| Last trade price: {last_trade_price:.2f}
| Best ask: {best_ask:.2f} | Best bid: {best_bid:.2f} | 
"""
        return custom_format_status
  • We are using the conditional to see if all markets are ready like the previous example
  • We are getting the mid price, best ask, best bid and last trade price by using the connector
  • Creating a multiline f-string to show all the variables that we want!
  • In this case, we are overriding the original format_status but you may also extend it by appending to the lines list with another list that contains custom strings.

Running the script

  • Run the command: start --script quickstart_script_2.py.
  • Run the command: status --live and you should see:

Alt text

Next steps

Next, we'll start to customize the market making script!

Creating a Custom Market Making Strategy - Part 4

Code: https://gist.github.com/cardosofede/b1c727c3645c5ba8622496fd87838598

Video:

In this exercise, let’s extend our market making script by adding a Price Ceiling/Floor feature.

But first, let’s gain some intuition for why this feature is important for market making strategies:

Motivation

Markets with low liquidity are easy to manipulate, as this real-life example shows. In less than 8 hours, the price in this trading pair has pumped 39% and then returned to the original price.

Alt text

If you were a naive market maker providing liquidity all the way through this pattern, you would likely have lost money since:

  • During the price uptrend, only your sell orders are executed
  • After the price peaks and the downtrend starts, only your buy orders are executed
  • Effectively, you sell your base asset at a lower price and buy it back at a higher price

One method to mitigate this risk is to define a price window that you will provide liquidity:

  • Below a floor price, the bot won’t place sell orders
  • Above a ceiling price, the bot won’t place buy orders

So how do we define these limits?

  • We can fix the floor and ceiling thresholds based on recent minimum and maximum price values.
  • Let’s select the following thresholds for this example:
    • price_ceiling: 0.0327
    • price_floor: 0.02736

Alt text

  • As shown above, the bot is protected when the price goes above 0.0327 because is not going to buy more tokens. Also, if the price goes below 0.02736 the bot is not going to sell the tokens.
  • In the next example, we’ll use a statistical approach to create dynamic price ceiling/floor parameters that adjust based on market conditions.

Let's code

We will extend the file used in the last example: quickstart_script_2.py

Let’s quickly recap the structure of the on_tick method:

    def on_tick(self):
    if self.create_timestamp <= self.current_timestamp:
        self.cancel_all_orders()
        proposal: List[OrderCandidate] = self.create_proposal()
    # HERE WE CAN CHECK IF THE ORDERS ARE INSIDE THE BOUNDS
        proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal)
        self.place_orders(proposal_adjusted)
        self.create_timestamp = self.order_refresh_time + self.current_timestamp

The comment location is where we can check if the price of the sell order is above the ceiling_price or if the price of the buy order is below the floor_price.

First, let’s add these new variables to the class:

import logging
from decimal import Decimal
from typing import List

from hummingbot.core.data_type.common import OrderType, PriceType, TradeType
from hummingbot.core.data_type.order_candidate import OrderCandidate
from hummingbot.core.event.events import OrderFilledEvent
from hummingbot.strategy.script_strategy_base import ScriptStrategyBase

class QuickstartScript2(ScriptStrategyBase):

 bid_spread = 0.008
  ask_spread = 0.008
  order_refresh_time = 15
  order_amount = 0.01
  create_timestamp = 0
  trading_pair = "ETH-USDT"
  exchange = "binance_paper_trade"
  # Here you can use for example the LastTrade price to use in your strategy
  price_source = PriceType.MidPrice

 price_ceiling = 1700
 price_floor = 1600

 markets = {exchange: {trading_pair}}

Tip

Select these values based on the current market prices, as this tutorial was last updated in Feb 2023.

apply_price_ceiling_floor_filter

Now let’s create a method to filter the orders:

def apply_price_ceiling_floor_filter(self, proposal):
    proposal_filtered = []
    for order in proposal:
        if order.order_side == TradeType.SELL and order.price > self.price_floor:
            proposal_filtered.append(order)
        elif order.order_side == TradeType.BUY and order.price < self.price_ceiling:
            proposal_filtered.append(order)
    return proposal_filtered

We add the new method to on_tick:

    def on_tick(self):
    if self.create_timestamp <= self.current_timestamp:
        self.cancel_all_orders()
        proposal: List[OrderCandidate] = self.create_proposal()
    proposal_filtered = self.apply_price_ceiling_floor_filter(proposal)
        proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal_filtered)
        self.place_orders(proposal_adjusted)
        self.create_timestamp = self.order_refresh_time + self.current_timestamp

Running the script

Start and stop the script as you did before, but change the values of price ceiling/floor and using status to check the current price ceiling/floor and whether your bot is placing orders correctly.

Test 1: Both orders inside range

  • Price ceiling: 1700
  • Price floor: 1600
  • Current mid-price: 1670
  • Sell order price: 1670 * 1.008 = 1683 (in range)
  • Buy order price: 1670 * 0.992 = 1656 (in range)

The bot places both BUY and SELL orders:

Alt text

Test 2: Buy orders out of range

  • Price floor: 1600
  • Price ceiling: 1600
  • Current mid-price: 1665
  • Sell order price: 1665 * 1.008 = 1678 (out of range)
  • Buy order price: 1665 * 0.992 = 1651 (in range)

The bot only places SELL orders:

Alt text

Next steps

In the final part of this guide, let's make this feature more dynamic based on market data!

Creating a Custom Market Making Strategy - Part 5

Code: https://gist.github.com/david-hummingbot/f9332923faac2fb5485eb7a80eb0d08d

Video:

Finally, let’s learn how to customize our script by utilizing order book data, leveraging Hummingbot’s ability to synchronously stream real-time Level 2 order book data from multiple exchanges simultaneously.

Dynamic calculation using Bollinger Bands

We will improve the last exercise by adding a dynamic calculation of the price ceiling/floor feature based on the Bollinger Bands.

To do so, we will:

  • Use the CandlesFactory object that will create an instance of the candles of the trading pair and interval that we want. Is important to notice that we can create as many candles as we want and they are not related to the markets that you define on the class variable markets. This is handy for example if you want to trade on KuCoin with the information of Binance.
  • Once we get the class of the candles, we need to start them. The start will initialize the WebSocket connection to receive the most updated information on the current candle, and also request the historical candles needed to complete the records requested. We are going to do this on the __init__.
  • Also we need to add a method to stop the candle when the script is stopped. We can override the method on_stop which let’s us code a custom shutdown when the bot is stopped via the stop command.
  • To calculate the bounds we are going to create a method that will add the Bollinger Bands using as a value 100 periods and 2 std. Then the upper bound will be price_ceiling and the lower bound will be price_floor.

Now, let’s implement the solution, extending the same file as the last example: quickstart_script_2.py.

Let's code!

First, we will create an candle instance:

import logging
from decimal import Decimal
from typing import List, Dict
import pandas as pd
import pandas_ta as ta  # noqa: F401

from hummingbot.core.data_type.common import OrderType, PriceType, TradeType
from hummingbot.core.data_type.order_candidate import OrderCandidate
from hummingbot.core.event.events import OrderFilledEvent
from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory, CandlesConfig
from hummingbot.strategy.script_strategy_base import ScriptStrategyBase

class QuickstartScript2(ScriptStrategyBase):
    bid_spread = 0.008
    ask_spread = 0.008
    order_refresh_time = 15
    order_amount = 0.01
    create_timestamp = 0
    trading_pair = "ETH-USDT"
    exchange = "binance_paper_trade"
    # Here you can use for example the LastTrade price to use in your strategy
    price_source = PriceType.MidPrice

    price_ceiling = 1700
    price_floor = 1600

    candles_config = CandlesConfig(connector="binance",
                                    trading_pair="ETH-USDT",
                                    interval="1m",
                                    max_records=500)

    eth_1m_candles = CandlesFactory.get_candle(candles_config)

    markets = {exchange: {trading_pair}}
  • We import the CandlesFactory & CandlesConfig class and call the get_candle method to create a candle instance in the variable eth_1m_candles.
  • Note that we are importing pandas_ta, a library that we will use to generate technical indicators from the candle data.
  • While we still define initial values for price_ceiling and price_floor, we will override them later in the on_tick method.

Add functions that override the __init__ and on_close methods:

def __init__(self, connectors: Dict[str, ConnectorBase]):
    # Is necessary to start the Candles Feed.
    super().__init__(connectors)
    self.eth_1m_candles.start()

The method above starts collecting data when the script starts.

def on_stop(self):
    """
    Without this functionality, the network iterator will continue running forever after stopping the strategy
    That's why is necessary to introduce this new feature to make a custom stop with the strategy.
    :return:
    """
    self.eth_1m_candles.stop()

The method above stops collecting data when the user runs the stop command.

calculate_pricing_ceiling_floor

Add a method that uses the data in eth_1m_candles to calculate moving price ceiling/floor using Bollinger Bands and update the values of the price_ceiling and price_floor.

def calculate_price_ceiling_floor(self):
    candles_df = self.eth_1m_candles.candles_df
    candles_df.ta.bbands(length=100, std=2, append=True)
    last_row = candles_df.iloc[-1]
    self.price_ceiling = last_row['BBU_100_2.0'].item()
    self.price_floor = last_row['BBL_100_2.0'].item()

Now, let’s add it to on_tick:

def on_tick(self):
    if self.create_timestamp <= self.current_timestamp and self.eth_1m_candles.is_ready:
        self.cancel_all_orders()
    self.calculate_price_ceiling_floor()
        proposal: List[OrderCandidate] = self.create_proposal()
    proposal_filtered = self.apply_price_ceiling_floor_filter(proposal)
        proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal_filtered)
        self.place_orders(proposal_adjusted)
        self.create_timestamp = self.order_refresh_time + self.current_timestamp
  • We are adding a new condition that will block the execution if the candles are not ready.
  • Then we are adding the method calculate_price_ceiling_floor and the implementation is accessing to the dataframe of the Candles object by using the method self.eth_1m_candles.candles_df
  • Lastly, we are getting the last row and assigning the upper and lower bound to price_ceiling and price_floor

Tip

Check out the pandas-ta documentation to learn how to generate other types of technical indicators.

Custom status

Before we run the script, let’s improve the status command and show the candles data, as well as the current values for the price_ceiling and price_floor.

def format_status(self) -> str:
    if not self.ready_to_trade:
        return "Market connectors are not ready."
    lines = []
    mid_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.MidPrice)
    best_ask = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.BestAsk)
    best_bid = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.BestBid)
    last_trade_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, PriceType.LastTrade)
    custom_format_status = f"""
| Mid price: {mid_price:.2f}| Last trade price: {last_trade_price:.2f}
| Best ask: {best_ask:.2f} | Best bid: {best_bid:.2f} 
| Price ceiling {self.price_ceiling:.2f} | Price floor {self.price_floor:.2f}
"""
    lines.extend([custom_format_status])
    if self.eth_1m_candles.is_ready:
        lines.extend([
            "\n############################################ Market Data ############################################\n"])
        candles_df = self.eth_1m_candles.candles_df
        # Let's add some technical indicators
        candles_df.ta.bbands(length=100, std=2, append=True)
        candles_df["timestamp"] = pd.to_datetime(candles_df["timestamp"], unit="ms")
        lines.extend([f"Candles: {self.eth_1m_candles.name} | Interval: {self.eth_1m_candles.interval}\n"])
        lines.extend(["    " + line for line in candles_df.tail().to_string(index=False).split("\n")])
        lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"])
    else:
        lines.extend(["", "  No data collected."])
    return "\n".join(lines)
  • We are using the list approach to add strings and then show all of them
  • First, we are adding to our original text the price ceiling and price floor
  • Then we are logging the information of the candles.
  • Check that when you run the code, the last candle is updated in real-time ;)

Running the script

Run the script with start again.

After it’s running, run status --live to see your dynamic price ceiling/floor along with the candles data streaming in real-time!

Alt text

You can display any variable you want here, so use it to analyze what is happening with your bot.

Debugging scripts

Watch this video to learn how to debug Scripts at runtime with the PyCharm IDE:


Congratulations on successfully building your own custom market making script!

If you’d like to learn more about how to build custom strategies using Hummingbot, check out Botcamp!

How To Get Good At Market Making

cover

by Lã Minh Hoàng

Hey guys, Wojak here!

I hope you're having a good day! Today, let's dive into the topic of how to get good at market making!

So, a little bit of introduction first! I am Wojak, an active member of the Miner community, and I have been competitively market making on the platform for the past year.

As we all know, market making is not simple. It's even harder to compete on the Hummingbot Miner platform. Getting good at market making requires knowledge of market dynamics and technical skills to scale up the business. In addition, your funds could be in grave danger if you don't have a good strategy to counter adverse market conditions.

Therefore, it would be great if a beginner could start their market making journey with a bag full of tips and tricks! Say no more; I'm here to help!

A Systematic Approach to Liquidity Mining

ROI-Based Analytics and Simple Experiments

by Diego C

cover

The end goal of Liquidity Mining strategies is to outperform traditional staking and farming yields in DEFI/CEFI using your crypto assets.

If you HODL crypto assets in exchanges like Binance, Kucoin, Gate.io, or Ascendex and are not using them for trading, staking, or yield farming on these platforms, you are missing out on potential passive income from these assets.

This is where Liquidity Mining comes into play:

Advantages:

  • Higher ROI
  • No lockup period for your assets

Disadvantages (or so):

  • Requirement of a trading bot

For this, Hummingbot has already created a ready-to-use trading bot for the special purposes of liquidity mining, pure market making, arbitrage, etc.

Capital Deployment with Hummingbot

Alt text

by Dalskie

I am delighted to share insights that have enabled us to expand our fund while providing a steady monthly income for our families. I extend heartfelt thanks to the Hummingbot Foundation & CoinAlpha for their support.

Though only a few in the Discord community know me, a brief personal introduction seems appropriate. My reticence in joining discussions stems from my shyness and limited knowledge in IT, crypto, and trading.

Previously, I worked as an architect in Singapore for over a decade before leaving my career to be with my child. The world of crypto presented an opportunity to fulfill my desire to spend more time with my family.

In April 2021, my husband discovered Hummingbot software, which enabled him to earn rewards by providing liquidity. This discovery reminded us of the early days when he mined ETH with graphic cards. However, I was unable to participate immediately as he was still perfecting his strategies.

The mid-year market crash of 2021 initially dampened our spirits, casting doubt on whether it was worthwhile to continue our investment in liquidity mining.

Strategy Coding for Dummies

cover

by Ben Smeaton

Hi! I've been using Hummingbot for about a year on and off and wanted to give an overview on how to actually go about defining your own strategies. I got into market making because of a hobbyist interest in python coding and I was at that time tinkering with buying and selling shares automatically on the stock market which was incredibly unsuccessful.

Hummingbot is very accessible for hobby coders due to its mainly python-based code base and the fact that its all open source (Meaning you can play around with the code). It's pretty easy to scrape together your own strategies although it�s taken quite a lot of trial and error to get there myself! I wanted to give a basic idea of how you can go about doing this in the simplest way possible.

Favorite Hummingbot Parameters from Trader Jazzy

Alt text

By Jazzy Obmaz

Hey guys, Jazzy here. I am officially helping my sister, Dalskie, operate the Hummingbot bots.

I'm not yet well-versed in crypto & trading, but I find it an intriguing and unbelievable world to live in. I am thrilled to have realized my long-time dream of working from home and being my own boss, so to speak. What a wonderful, life-changing opportunity this is for my family, especially financially. A huge thank you to Hummingbot!

Regarding our bot strategy, we are using the Pure Market Making (PMM) Strategy.

Okay, a reminder: this post is NOT financial advice, and any user following it will be solely responsible for his/her own results. Please don't blame me for any future losses you may experience. It's not because of me, Hummingbot, or the strategy; it's due to people panic selling. Period.

So, why the PMM strategy? Well, it works and it's simple. I have learned to use it even without a nerdy background. It's not that I am super smart or anything; it's thanks to the software or strategy itself. And of course, thanks to Dalskie.

I am now about to share the favorite parameters that I set for all bots. Roll up your sleeves, guys; let's get officially started.

The Orders Must Flow from Trader Mobiwan

Alt text

by Mohammed Badran

In the fictional world of Dune, the most precious substance in the universe is the spice. The spice extends life, expands consciousness, and is vital to space travel.

Likewise, in the very real world of liquidity mining, the most precious substance in the market is the order. The order allows trading, enables market making, and is vital to liquidity rewards.

cover

by Thomas Yit

Understanding and detecting market trends (or lack thereof) is an important skill for any trader, especially market-makers. As mentioned in the past two articles, like this one that talks about inventory risk, a market without a clear directional trend (aka moving sideways) is the market-maker’s best friend.

There are many ways to identify trends, either using fundamental analysis or technical analysis. In the following sections, I will further breakdown Fundamental and Technical Analysis and share some tools that hopefully will assist in building your framework for identifying trends.

Here is what you will find below:

  • What are Fundamental Analysis and Technical Analysis?
  • What is On-Chain Analysis?
  • How are they used on cryptocurrency markets?
  • How to use Technical Indicators to find the current market trend