An Overview of Market Making Strategies in Crypto: Architecture Design and FMZ Implementation of the Self-Matching Trading Strategy

in #fmz9 hours ago

⚠️ Important Disclaimer

This article demonstrates a volume-boosting self-matching trading strategy intended solely for learning purposes related to trading system architecture. It fundamentally differs from traditional arbitrage or market-making strategies. The core idea of this strategy is to simulate volume through same-price buy-sell matching to obtain exchange rebates or tier-based trading fee discounts, rather than profiting from market inefficiencies.

The provided code is only a reference framework and has not been tested in live trading environments. The strategy implementation is purely for technical study and research, and has not been thoroughly validated under real market conditions. Readers must perform extensive backtesting and risk evaluation before considering any real-world application. Do not use this strategy for live trading without proper due diligence.


In the cryptocurrency market, market-making strategies serve not only to improve liquidity and facilitate trading, but also form a core component of many quantitative trading systems. Market makers quote both buy and sell prices, provide liquidity, and capture profits under various market conditions. Professional market-making systems are often highly sophisticated, involving ultra-low-latency optimization, advanced risk management modules, and cross-exchange arbitrage mechanisms. In this article, we explore the basic concept behind a volume-boosting self-matching trading strategy and demonstrate how to implement a simplified educational framework on the FMZ Quant Trading Platform.

The main content is adapted from the original work "Market Making Strategy: Concepts and Implementation" by Zinan, with some optimizations and adjustments. While some coding practices may now appear outdated, this version recreated on the FMZ platform still offers valuable insights into the structure of trading algorithms and the fundamentals of self-matching trading logic.

Concept of Market Making Strategies

A Market Making Strategy refers to a trading approach where a trader (market maker) places both buy and sell orders in the market simultaneously, thereby providing liquidity and contributing to market stability. This strategy not only helps maintain market depth but also offers counterparties for other traders. By quoting buy and sell prices across various price levels, market makers aim to profit from market fluctuations.

In the context of cryptocurrency markets, market makers play a critical role—especially in markets with low trading volume or high volatility. By offering liquidity, market makers help reduce slippage and make it easier for other traders to execute orders at favorable prices.

The core principle of traditional market-making strategies lies in capturing the bid-ask spread by providing liquidity. Market makers post buy orders at lower prices and sell orders at higher prices, profiting from the difference when orders are filled. For example, when the spot price in the market rises, market makers sell at a higher price and buy at a lower price, earning the difference. The primary sources of income include:

  • Bid-ask spread: Traditional market makers earn profits by placing limit buy and sell orders and capturing the price difference between the bid and ask.
  • Volume-based incentives: The profitability of market makers is closely tied to the trading volume they provide. Higher volume not only leads to more frequent order fills and profit opportunities but also unlocks additional benefits:
    1). Fee rebates: Many exchanges offer fee rebates to incentivize liquidity provision. In some cases, makers receive negative fees—meaning the exchange pays the market maker for executed trades.
    2). VIP tier discounts: Reaching specific volume thresholds may qualify market makers for lower trading fees, reducing operational costs.
    3). Market maker incentive programs: Some exchanges run dedicated incentive programs that reward market makers based on the quality and consistency of their liquidity provision.

However, market makers also face significant market risk, especially in highly volatile environments such as the cryptocurrency space. Rapid price swings may cause market makers’ quoted prices to deviate significantly from actual market conditions, potentially resulting in losses.

Types of Market Making Strategies

In the cryptocurrency market, market makers typically choose different strategies based on market conditions, trading volume, and volatility. Common types of market-making strategies include:

  • Passive market making: In this approach, the market maker places buy and sell limit orders based on market depth, historical volatility, and other indicators, then waits for the market to fill those orders. This strategy is characterized by low frequency and a conservative risk profile, relying on natural market movements to generate profit.

  • Active market making: Active market makers dynamically adjust order prices and sizes in real time based on market conditions to improve execution probability. They often place orders close to the current mid-price, aiming to better capture opportunities from short-term volatility.

  • Volume-boosting self-matching strategy: The focus of this article. A volume-boosting self-matching strategy involves placing simultaneous buy and sell orders at the same price to artificially increase trading volume. Unlike traditional market-making, this strategy is not designed to profit from bid-ask spreads. Instead, it seeks to benefit from exchange incentives such as fee rebates, VIP tier discounts, or liquidity mining rewards.

In a volume-boosting self-matching strategy, the market maker posts buy and sell orders at the same price level. While these trades do not generate profits from price differentials, they quickly accumulate trading volume. Profitability is entirely dependent on the exchange's incentive mechanisms rather than market inefficiencies or arbitrage.

Key characteristics:

  • Same-price orders: Unlike traditional market-making, buy and sell orders are placed at the same price, eliminating spread-based profits.

  • Volume-oriented execution: The primary objective is to accumulate trade volume rapidly, not to exploit price arbitrage.

  • Incentive-driven profitability: Returns are fully reliant on exchange incentives such as fee rebates, VIP tier benefits, or dedicated market maker reward programs.

Important distinction:
Compared to traditional market-making strategies, volume-boosting self-matching strategies do not generate profit by providing genuine market liquidity. Instead, they create trading volume artificially to capitalize on policy-driven rewards offered by exchanges. This type of strategy may carry regulatory or compliance risks in certain jurisdictions and must be carefully evaluated before any live trading deployment.

Profit Logic Behind Volume-Boosting Self-Matching Strategies

Upon analyzing the code implementation, it becomes evident that the buy and sell prices are exactly the same in this strategy:

def make_duiqiao_dict(self, trade_amount):
    mid_price = self.mid_price  # Med price
    trade_price = round(mid_price, self.price_precision)  # Accurate transaction price
    trade_dict = {
        'trade_price': trade_price,  # The same price is used for both buying and selling
        'amount': trade_amount
    }
    return trade_dict

Actual Profit Mechanism

1. Volume-boosting via self-matching

  • The core objective of this strategy is to increase trading volume rapidly through high-frequency self-matching.
  • Profit is derived from exchange incentives such as fee rebates, VIP tier upgrades, or liquidity mining rewards.
  • This approach is applicable on exchanges that offer formal market maker incentive programs.

2. Fee rebate mechanism

  • The strategy relies on exchanges offering negative maker fees (maker rates are negative)
  • By providing liquidity, the strategy earns rebates on filled orders.
  • It requires the exchange to support market maker fee discounts or rebate structures.

Suitable Scenarios & Associated Risks

✅ Suitable scenarios

  • Exchanges with clear market maker rebate or incentive policies.
  • Traders aiming to meet high trading volume requirements for VIP tier upgrades.
  • Platforms that run liquidity mining or commission rebate campaigns.

❌ Unsuitable scenarios

  • Exchanges that do not offer fee rebates or incentives.
  • Platforms with high transaction fees, where self-matching leads to net losses.
  • Exchanges that explicitly prohibit wash trading or enforce restrictions on artificial volume.

⚠️ Risk warnings

  • If both buy and sell orders are filled simultaneously, the strategy may incur a net loss after fees.
  • Changes in exchange policies may render the strategy unprofitable or non-viable.
  • Continuous monitoring of fee structures and trading costs is required.
  • The strategy may face compliance risks in jurisdictions where volume-boosting trading is regulated or restricted.

Self-Matching Strategy Architecture Analysis

This section presents a simplified implementation of a volume-boosting self-matching strategy, inspired by the framework developed by Zinan. The focus is on how to accumulate trading volume through same-price buy and sell orders within a live exchange environment. The strategy architecture is structured around two main classes: MidClass and MarketMaker. These components are responsible for handling exchange interactions and executing the self-matching logic, respectively.

The architecture follows a layered design, separating the exchange interface logic from the trading strategy logic. This ensures modularity, extensibility, and clean separation of concerns. The main components are:

  1. MidClass: The exchange middle layer is responsible for interacting with the exchange interface to obtain market data, account information, order status, etc. This layer encapsulates all interactions with external exchanges to ensure that the trading logic and the exchange interface are decoupled.
  2. MarketMaker: The market making strategy class is responsible for executing the cross-trading strategy, generating pending orders, checking order status, updating strategy status, etc. It interacts with the exchange middle layer to provide specific market making and self-matching trading operations.

MidClass

MidClass is the middle layer of the exchange. Its main responsibility is to handle the interaction with the exchange, encapsulate all external API calls, and provide a simple interface for MarketMaker to use. Its architecture includes the following key functions:

1. Market data retrieval:
Fetches real-time market data such as tickers, order book depth, and candlestick data (K-lines). Regular updates are essential to ensure the strategy operates on up-to-date information.

2. Account information management:
Retrieves account data, including balances, available margin, and open positions. This is critical for capital allocation and risk control.

3. Order management:
Provides functionality to place, query, and cancel orders. This is the foundation of executing a market-making or self-matching strategy and ensures robust control over open orders.

4. Data Synchronization:
Maintains persistent connections with the exchange and updates internal state for use by the strategy layer.

By encapsulating these features in MidClass, the strategy logic within MarketMaker remains focused on execution rather than infrastructure. This structure improves the maintainability and scalability of the system, making it easier to add support for different exchanges or optimize existing functions.

MarketMaker

MarketMaker is the core class of the self-matching strategy, responsible for executing market-making operations and handling self-matching trades. Its architecture typically includes the following key modules:

1. Initialization:

  • Initialize the exchange middleware (MidClass) to retrieve exchange metadata such as trading pair details, precision, tick size, and order book depth.
  • Initialize the market-making strategy, set up key parameters like order quantity, price spread, and execution intervals. These parameters directly affect how the strategy behaves and performs in the market.

2. Data refresh:

  • Periodic market data updates, including real-time account information, market price, depth, K-line, etc. These data provide basic information for executing strategies.
  • The frequency of updates can be dynamically adjusted based on market volatility to ensure timely responses to market changes.

3. Self-matching execution logic:

  • Order book construction: Based on current market depth and price dynamics, construct a dictionary of orders (both bids and asks) with specified price and size. This is typically calculated using predefined strategy parameters.
  • Self-matching execution: Once the order structure is ready, MarketMaker submits both buy and sell orders at the same price level to the market. The goal is to accumulate trade volume quickly via same-price order matching.
  • Order status monitoring: During execution, MarketMaker will check the status of the order constantly to ensure that the pending order can be processed in time. If the order fails to be executed, it will adjust the pending order price or quantity until the order is completed.

4. Strategy state update:

  • Strategy status update: Regularly update key performance indicators such as cumulative trading volume, filled order count, and total fees. These metrics allow real-time tracking of the strategy's performance.
  • Dynamic risk management: The strategy adapts its behavior based on current market conditions. MarketMaker can modify execution logic in real time to reduce risk and maintain operational efficiency across varying market environments.

Self-Matching Strategy Logic Reconstruction

The implementation of a self-matching strategy relies on precise market data and fast execution. The MarketMaker class monitors real-time market conditions and leverages same-price buy and sell orders (self-matching) to rapidly accumulate trading volume, which is the core objective of this strategy.

Initialization

In the MarketMaker class's initialization method, the first step is to retrieve the exchange's precision settings, followed by initializing key strategy parameters such as quantity precision and price precision.

self.precision_info = self.exchange_mid.get_precision()  # Get precision information
self.price_precision = self.precision_info['price_precision']  # Price precision
self.amount_precision = self.precision_info['amount_precision']  # Trading volume precision

Generating the Self-Matching Order Dictionary

At the heart of the self-matching strategy is the construction of an order dictionary containing both buy and sell orders at the same price level, along with their respective quantities. The code generates the dictionary of self-matching trading orders by calculating the middle price.

def make_duiqiao_dict(self, trade_amount):
    mid_price = self.mid_price  # Mid Price
    trade_price = round(mid_price, self.price_precision)  # Accurate transaction price
    trade_dict = {
        'trade_price': trade_price,
        'amount': trade_amount
    }
    return trade_dict

Executing Self-Matching Trades

According to the generated dictionary of self-matching trading orders, the self-matching trading transaction is executed. In the code, the create_order method of the exchange middle layer is called to place buy orders and sell orders at the same time.

def make_trade_by_dict(self, trade_dict):
    if self.position_amount > trade_dict['amount'] and self.can_buy_amount > trade_dict['amount']:
        buy_id = self.exchange_mid.create_order('buy', trade_dict['trade_price'], trade_dict['amount'])  # Pending buy order
        sell_id = self.exchange_mid.create_order('sell', trade_dict['trade_price'], trade_dict['amount'])  # Pending sell order
        self.traded_pairs['dui_qiao'].append({
            'buy_id': buy_id, 'sell_id': sell_id, 'init_time': time.time(), 'amount': trade_dict['amount']
        })

Order Status Monitoring

The strategy periodically checks the status of active orders and handles any unfilled or partially filled ones. In the code, this is done by calling the GetOrder method from the exchange middleware (MidClass). Based on the returned order status, the strategy decides whether to cancel the orders. The self-matching order management logic includes the following key steps:

1. Fetching order status:

  • The strategy retrieves the current status of both the buy and sell orders through the exchange API.
  • If the status retrieval fails (e.g., due to a missing order or network issue), the strategy cancels the corresponding order and removes it from the active tracking list.

2. Evaluating order status:

  • The status returned is used to determine whether the order is filled, partially filled, or still open.
  • Typical order status include:
    0(ORDER_STATE_PENDING): Order is open and waiting to be filled.
    1(ORDER_STATE_CLOSED): Order has been completely filled.
    2(ORDER_STATE_CANCELED): Order has been canceled.
    3(ORDER_STATE_UNKNOWN): Order status is unknown or undefined.

3. Handling order status:

  • Both orders unfilled:
    If both buy and sell orders remain unfilled (status 0), the strategy checks the polling interval (e.g., current_time % 5 == 0) to decide whether to cancel them.
    After cancellation, the strategy updates the order count and removes the orders from the internal record.

  • One order filled, the other unfilled:
    If one side of the self-matching order pair is filled (status == 1) and the other remains unfilled (status == 0), the strategy uses the polling interval condition to decide whether to cancel the unfilled order.
    After cancelling an open order, update the volume and the list of open orders and remove the order from the record.

  • Both orders filled:
    If both the buy and sell orders are fully executed (status == 1), the strategy updates the trade volume counter, and the order pair is removed from the internal tracking list.

  • Unknown order status:
    If the order status is neither 0 nor 1, it is recorded as unknown status and logged.

4. Updating internal records:
After processing the order statuses, the strategy updates the total accumulated trade volume, the list of unfilled or partially filled orders, the order submission and cancellation counters.

Future Strategy Outlook

The self-matching strategy presented in this article is primarily intended as an educational example for understanding the architectural design of trading frameworks. Its practical application in live trading is limited. For readers interested in real market-making strategies, we plan to introduce more advanced and practical models in future content:

1. Order book market-making strategy

  • A true arbitrage-based approach that captures the bid-ask spread.
  • Places limit orders between the best bid and ask to earn the spread profit.
  • Closely aligns with the traditional profit model of professional market makers.

2. Dynamic market-making strategy

  • Adapts quote prices in real-time based on market volatility.
  • Integrates inventory management and risk control mechanisms.
  • Suitable for adaptive execution across varying market conditions.

3. Multi-level market-making strategy

  • Places orders at multiple price levels simultaneously.
  • Diversifies execution risk and enhances overall return stability.
  • Closer to how professional market-making systems operate in production.

These upcoming strategies will emphasize realistic profit logic and robust risk management, providing quantitative traders with more actionable and valuable insights for developing production-ready systems.

Strategy Outlook

Self-matching strategies that rely on exchange incentive policies, such as fee rebates, VIP tier upgrades, or liquidity mining rewards — are inherently vulnerable to changes in those policies. If an exchange adjusts its fee structure or removes such incentives, the strategy may become ineffective or even result in net losses. To mitigate this, the strategy must incorporate adaptability to policy changes, such as dynamic monitoring of fee rates and trading incentives, multiple profit sources to reduce over-reliance on a single incentive. fallback mechanisms or automatic shutdown triggers if profitability thresholds are not met. Moreover, self-matching strategies may raise regulatory red flags, as they can be interpreted as attempts to manipulate market volume. In many jurisdictions, such behavior may violate market integrity laws.

Therefore, traders must stay updated on local legal and compliance requirements, consult with legal professionals when deploying volume-based strategies, avoid practices that could be construed as deceptive or manipulative.

We hope readers use this strategy framework as a foundation to build more robust, compliant, and innovative trading systems. The true value of quantitative trading lies in continuous learning, experimentation, and refinement. May your journey in quant trading be insightful, adaptive, and rewarding!

Strategy Code

import time, json

class MidClass:
    def __init__(self, exchange_instance):
        '''
        Initialize the exchange middle layer
        
        Args:
            exchange_instance: FMZ's exchange structure
        '''
        self.init_timestamp = time.time()  # Record initialization time
        self.exchange = exchange_instance  # Save the exchange object
        self.exchange_name = self.exchange.GetName()  # Get the exchange name
        self.trading_pair = self.exchange.GetCurrency()  # Get the trading pair name (such as BTC_USDT)

    def get_precision(self):
        '''
        Get the accuracy information of the trading pair
        
        Returns:
            Returns a dictionary containing precision information, or None on failure.
        '''
        symbol_code = self.exchange.GetCurrency()
        ticker = self.exchange.GetTicker(symbol_code)  # Backtesting system needs
        exchange_info = self.exchange.GetMarkets()
        data = exchange_info.get(symbol_code)

        if not data:
            Log("Failed to obtain market information", GetLastError())
            return None

        # Get the accuracy information of the trading pair
        self.precision_info = {
            'tick_size': data['TickSize'],                  # Price accuracy
            'amount_size': data['AmountSize'],              # Quantity accuracy
            'price_precision': data['PricePrecision'],      # Price decimal places precision
            'amount_precision': data['AmountPrecision'],    # Number of decimal places of precision
            'min_qty': data['MinQty'],                      # Minimum order quantity
            'max_qty': data['MaxQty']                       # Maximum order quantity
        }

        return self.precision_info

    def get_account(self):
        '''
        Get account information
        
        Returns:
            Returns True if the information is successfully obtained, and returns False if the information is failed.
        '''

        self.balance = '---'  # Account balance
        self.amount = '---'  # Account holdings
        self.frozen_balance = '---'  # Freeze balance
        self.frozen_stocks = '---'  # Freeze positions
        self.init_balance = None
        self.init_stocks = None
        self.init_equity = None

        try:
            account_info = self.exchange.GetAccount()  # Get account information
            self.balance = account_info['Balance']  # Update account balance
            self.amount = account_info['Stocks']  # Update the holdings
            self.frozen_balance = account_info['FrozenBalance']  # Update frozen balance
            self.frozen_stocks = account_info['FrozenStocks']  # Update frozen positions
            self.equity = self.balance + self.frozen_balance + (self.amount + self.frozen_stocks) * self.last_price
            
            if not self.init_balance or not self.init_stocks or not self.init_equity:
                if _G("init_balance") and _G("init_balance") > 0 and _G("init_stocks") and _G("init_stocks") > 0:
                    self.init_balance = round(_G("init_balance"), 2)
                    self.init_stocks = round(_G("init_stocks"), 2)
                    self.init_equity = round(_G("init_equity"), 2)
                else:
                    self.init_balance = round(self.balance + self.frozen_balance, 2)
                    self.init_stocks = self.amount + self.frozen_stocks
                    self.init_equity = round(self.init_balance + (self.init_stocks * self.last_price), 2)
                    _G("init_balance", self.init_balance)
                    _G("init_stocks", self.init_stocks)
                    _G("init_equity", self.init_equity)

                    Log('Obtaining initial equity', self.init_equity)

            self.profit = self.equity - self.init_equity
            self.profitratio = round((self.equity - self.init_equity)/self.init_equity, 4) * 100

            return True
        except:
            return False  # Failed to obtain account information

    def get_ticker(self):
        '''
        Get market price information (such as bid price, ask price, highest price, lowest price, etc.)
        
        Returns:
            Returns True if the information is successfully obtained, and returns False if the information is failed.
        '''
        self.high_price = '---'  # The highest price
        self.low_price = '---'  # The lowest price
        self.sell_price = '---'  # Ask price
        self.buy_price = '---'  # Bid price
        self.last_price = '---'  # Latest transaction price
        self.volume = '---'  # Trading volume
        
        try:
            ticker_info = self.exchange.GetTicker()  # Get market price information
        
            self.high_price = ticker_info['High']  # Update highest price
            self.low_price = ticker_info['Low']  # Update lowest price
            self.sell_price = ticker_info['Sell']  # Update ask price
            self.buy_price = ticker_info['Buy']  # Update bid price
            self.last_price = ticker_info['Last']  # Update the latest transaction price
            self.volume = ticker_info['Volume']  # Update trading volume
            return True
        except:
            return False  # Failed to obtain market price information
        
    def get_depth(self):
        '''
        Get depth information (list of pending orders for buy and sell orders)
        
        Returns:
            Returns True if the information is successfully obtained, and returns False if the information is failed.
        '''
        self.ask_orders = '---'  # Ask order list
        self.bid_orders = '---'  # Bid order list
        
        try:
            depth_info = self.exchange.GetDepth()  # Get depth information
            self.ask_orders = depth_info['Asks']  # Update the sell order list
            self.bid_orders = depth_info['Bids']  # Update buy order list
            return True
        except:
            return False  # Failed to obtain depth information
        
    def get_ohlc_data(self, period=PERIOD_M5):
        '''
        Get K-line information
        
        Args:
            period: K-line period, PERIOD_M1 refers to 1 minute, PERIOD_M5 refers to 5 minutes, PERIOD_M15 refers to 15 minutes,
            PERIOD_M30 means 30 minutes, PERIOD_H1 means 1 hour, PERIOD_D1 means one day.
        '''
        self.ohlc_data = self.exchange.GetRecords(period)  # Get K-line data
        
    def create_order(self, order_type, price, amount):
        '''
        Submit an order
        
        Args:
            order_type: Order type, 'buy' refers to a buy order, 'sell' refers to a sell order
            price: Order price
            amount: Order amount 
            
        Returns:
            Order ID number, which can be used to cancel the order
        '''
        if order_type == 'buy':
            try:
                order_id = self.exchange.Buy(price, amount)  # Submit a buy order
            except:
                return False  # Buy order submission failed
            
        elif order_type == 'sell':
            try:
                order_id = self.exchange.Sell(price, amount)  # Submit a sell order
            except:
                return False  # Sell order submission failed
        
        return order_id  # Returns the order ID
    
    def get_orders(self):
        '''
        Get a list of uncompleted orders
        
        Returns:
            List of uncompleted orders
        '''
        self.open_orders = self.exchange.GetOrders()  # Get uncompleted orders
        return self.open_orders
    
    def cancel_order(self, order_id):
        '''
        Cancel a pending order
        
        Args:
            order_id: The ID number of the pending order you wish to cancel
            
        Returns:
            Returns True if the pending order is successfully cancelled, and returns False if the pending order is failed.
        '''
        return self.exchange.CancelOrder(order_id)  # Cancel the order
        
    def refresh_data(self):
        '''
        Refresh information (account, market price, depth, K-line)
        
        Returns:
            If the refresh information is successfully returned, 'refresh_data_finish!' will be returned. Otherwise, the corresponding refresh failure information prompt will be returned.
        '''

        if not self.get_ticker():  # Refresh market price information
            return 'false_get_ticker'

        if not self.get_account():  # Refresh account information
            return 'false_get_account'
        
        if not self.get_depth():  # Refresh depth information
            return 'false_get_depth'
        
        try:
            self.get_ohlc_data()  # Refresh K-line information
        except:
            return 'false_get_K_line_info'
        
        return 'refresh_data_finish!'  # Refresh successfully

class MarketMaker:
    def __init__(self, mid_class):
        '''
        Initialize market making strategy
        
        Args:
            mid_class: Exchange middle layer object
        '''
        self.exchange_mid = mid_class  # Exchange middle layer object
        self.precision_info = self.exchange_mid.get_precision()  # Get accuracy information

        self.done_amount = {'dui_qiao': 0}  # Completed transactions
        self.price_precision = self.precision_info['price_precision']  # Price precision
        self.amount_precision = self.precision_info['amount_precision']  # Trading volume precision
        
        self.traded_pairs = {'dui_qiao': []}  # Trading pairs with pending orders
        self.pending_orders = []  # Uncompleted order status
        self.pending_order_count = 0  # Number of pending orders

        self.buy_amount = 0
        self.sell_amount = 0

        self.fee = 0
        self.fee_rate = 0.08 / 100

        self.chart = {
            "__isStock": True,
            "tooltip": {"xDateFormat": "%Y-%m-%d %H:%M:%S, %A"},
            "title": {"text": "Number of pending orders"},
            "xAxis": {"type": "datetime"},
            "yAxis": {
                "title": {"text": "Number of pending orders"},
                "opposite": False
            },
            "series": [
                {"name": "Buy order quantity", "id": "Buy order quantity", "data": []},
                {"name": "Sell order quantity", "id": "Sell order quantity", "dashStyle": "shortdash", "data": []}
            ]
        }
    
    def refresh_data(self):
        '''
        Refresh data (account, market price, depth, K-line)
        '''
        self.exchange_mid.refresh_data()  # Refresh exchange data
        self.position_amount = 0 if isinstance(self.exchange_mid.amount, str) else self.exchange_mid.amount  # Holding positions
        self.available_balance = 0 if isinstance(self.exchange_mid.balance, str) else self.exchange_mid.balance  # Account balance
        Log('Check ticker', self.exchange_mid.buy_price)
        self.can_buy_amount = self.available_balance / float(self.exchange_mid.buy_price)  # Quantity available for purchase
        self.mid_price = (self.exchange_mid.sell_price + self.exchange_mid.buy_price) / 2  # Mid Price

    def make_duiqiao_dict(self, trade_amount):
        
        '''
        Generate a dictionary of self-matching orders
        
        Args:
            trade_amount: Volume per transaction
        
        Returns:
            Dictionary list of self-matching orders
        '''
        Log('3 Create a dictionary for self-matching orders')

        mid_price = self.mid_price  # Mid price

        trade_price = round(mid_price, self.price_precision)  # Accurate transaction price

        trade_dict = {
            'trade_price': trade_price,
            'amount': trade_amount
        }

        Log('Returns the market order dictionary:', trade_dict)
        return trade_dict
    
    def make_trade_by_dict(self, trade_dict):
        '''
        Execute transactions according to the transaction dictionary
        
        Args:
            trade_dict: transaction dictionary
        '''
        Log('4 Start trading by dictionary')
        self.refresh_data()  # Refresh data
        
        if trade_dict:
            Log('Current account funds: Coin balance: ', self.position_amount, 'Funds balance: ', self.can_buy_amount)
            Log('Check open positions: Coin limit: ', self.position_amount > trade_dict['amount'], 'Funding restrictions: ', self.can_buy_amount > trade_dict['amount'])
            if self.position_amount > trade_dict['amount'] and self.can_buy_amount > trade_dict['amount']:
                buy_id = self.exchange_mid.create_order('buy', trade_dict['trade_price'], trade_dict['amount'])  # Pending buy order
                sell_id = self.exchange_mid.create_order('sell', trade_dict['trade_price'], trade_dict['amount'])  # Pending sell order
                
                self.traded_pairs['dui_qiao'].append({
                    'buy_id': buy_id, 'sell_id': sell_id, 'init_time': time.time(), 'amount': trade_dict['amount']
                })
                    
                self.last_time = time.time()  # Update last transaction time
        
    def handle_pending_orders(self):
        '''
        Processing unfulfilled orders
        '''
        pending_orders = self.exchange_mid.get_orders()  # Get uncompleted orders
        if len(pending_orders) > 0:
            for order in pending_orders:
                self.exchange_mid.cancel_order(order['Id'])  # Cancel uncompleted orders

    def check_order_status(self, current_time):
        '''
        Check order status
        current_time: Polling check times
        '''
        Log('1 Start order information check')
        Log(self.traded_pairs['dui_qiao'])
        self.buy_pending = 0
        self.sell_pending = 0
        for traded_pair in self.traded_pairs['dui_qiao'].copy():
            Log('Check the order:', traded_pair['buy_id'], traded_pair['sell_id'])

            try:
                buy_order_status = self.exchange_mid.exchange.GetOrder(traded_pair['buy_id'])  # Get buy order status
                sell_order_status = self.exchange_mid.exchange.GetOrder(traded_pair['sell_id'])  # Get sell order status
            except:
                Log(traded_pair, 'cancel')
                self.exchange_mid.cancel_order(traded_pair['buy_id'])  # Cancel buy order
                self.exchange_mid.cancel_order(traded_pair['sell_id'])  # Cancel sell order
                self.traded_pairs['dui_qiao'].remove(traded_pair)  # Remove order
                return

            Log('Check the order:', traded_pair['buy_id'], buy_order_status, traded_pair['sell_id'], sell_order_status, [sell_order_status['Status'], buy_order_status['Status']])
            if [sell_order_status['Status'], buy_order_status['Status']] == [0, 0]:
                self.buy_pending += 1
                self.sell_pending += 1
                if current_time % 5 == 0:
                    Log('Check pending orders and cancel pending orders (two unfinished)', buy_order_status['Status'], sell_order_status['Status'], current_time % 5)
                    self.exchange_mid.cancel_order(traded_pair['buy_id'])  # Cancel buy order
                    self.exchange_mid.cancel_order(traded_pair['sell_id'])  # Cancel sell order
                    self.pending_order_count += 1  # The number of pending orders increases by 1
                    self.traded_pairs['dui_qiao'].remove(traded_pair)  # Remove order

            elif {sell_order_status['Status'], buy_order_status['Status']} == {1, 0}:
                if buy_order_status['Status'] == ORDER_STATE_PENDING:
                    self.buy_pending += 1
                if sell_order_status['Status'] == ORDER_STATE_PENDING:
                    self.sell_pending += 1
                if current_time % 5 == 0:
                    Log('Check pending orders and cancel pending orders (part one is not yet completed)', buy_order_status['Status'], sell_order_status['Status'])
                    self.done_amount['dui_qiao'] += traded_pair['amount']  # Update transaction volume
                    if buy_order_status['Status'] == ORDER_STATE_PENDING:
                        self.sell_amount += traded_pair['amount']
                        self.fee += sell_order_status['Amount'] * self.fee_rate * sell_order_status['Price']
                        Log('Cancel the buy order and add the unfinished buy list', traded_pair['buy_id'])
                        self.exchange_mid.cancel_order(traded_pair['buy_id'])  # Cancel buy order
                        self.pending_orders.append(['buy', buy_order_status['Status']])  # Record uncompleted orders
                        Log('Before clearing:', self.traded_pairs['dui_qiao'])
                        Log('Clear id:', traded_pair)
                        self.traded_pairs['dui_qiao'].remove(traded_pair)  # Remove order
                        Log('After clearing:', self.traded_pairs['dui_qiao'])
                    elif sell_order_status['Status'] == ORDER_STATE_PENDING:
                        self.buy_amount += traded_pair['amount']
                        self.fee += buy_order_status['Amount'] * self.fee_rate * buy_order_status['Price']
                        Log('Cancel the sell order and add it to the unfinished sell list', traded_pair['sell_id'])
                        self.exchange_mid.cancel_order(traded_pair['sell_id'])  # Cancel sell order
                        self.pending_orders.append(['sell', sell_order_status['Status']])  # Record uncompleted orders
                        Log('Before clearing:', self.traded_pairs['dui_qiao'])
                        Log('Clear id:', traded_pair)
                        self.traded_pairs['dui_qiao'].remove(traded_pair)  # Remove order
                        Log('After clearing:', self.traded_pairs['dui_qiao'])
                
            elif [sell_order_status['Status'], buy_order_status['Status']] == [1, 1]:
                Log('Both orders have been completed')
                self.buy_amount += traded_pair['amount']
                self.sell_amount += traded_pair['amount']
                self.fee += buy_order_status['Amount'] * self.fee_rate * buy_order_status['Price']
                self.fee += sell_order_status['Amount'] * self.fee_rate * sell_order_status['Price']
                Log('Completion status:', buy_order_status['Status'], sell_order_status['Status'], traded_pair['amount'])
                self.done_amount['dui_qiao'] += 2 * traded_pair['amount']  # Update transaction volume
                self.traded_pairs['dui_qiao'].remove(traded_pair)  # Remove order
            else:
                Log('Two orders are in unknown status:', buy_order_status, sell_order_status)
                Log('Unknown order status:', buy_order_status['Status'], sell_order_status['Status'])
                Log('Unknown order information:', traded_pair)
        
    def update_status(self):

        self.exchange_mid.refresh_data()

        table1 = {
            "type": "table",
            "title": "Account information",
            "cols": [
                "Initial funds", "Existing funds", "Self-matching buy amount", "Self-matching sell  amount", "Fee rate", "Total return", "Rate of return"
            ],
            "rows": [
                [   
                    self.exchange_mid.init_equity,
                    self.exchange_mid.equity,
                    round(self.buy_amount, 4),
                    round(self.sell_amount, 4),
                    round(self.fee, 2),
                    self.exchange_mid.profit,
                    str(self.exchange_mid.profitratio) + "%"
                ],
            ],
        }

        LogStatus(
            f"Initialization time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.exchange_mid.init_timestamp))}\n",
            f"`{json.dumps(table1)}`\n",
            f"Last execution time: {_D()}\n"
        )

        LogProfit(round(self.exchange_mid.profit, 3), '&')

    def plot_pending(self):
        
        Log('Number of knock-on orders:', self.buy_pending, self.sell_pending)
        self.obj_chart = Chart(self.chart)
        now_time = int(time.time() * 1000)
        # Update pending order buy volume data
        self.obj_chart.add(0, [now_time, self.buy_pending])
        # Update pending order selling volume data
        self.obj_chart.add(1, [now_time, self.sell_pending])


def main():
    '''
    Main function, running market making strategy
    '''
    exchange.IO('simulate', True)
    exchange.IO("trade_super_margin")
    
    target_amount = 1  # Target transaction volume
    trade_amount = 0.01  # Volume per transaction
    trade_dict = {}  # Initialize transaction dictionary
    
    exchange_mid = MidClass(exchange)  # Initialize the exchange middle layer
    Log(exchange_mid.refresh_data())  # Refresh data
    market_maker = MarketMaker(exchange_mid)  # Initialize market making strategy

    check_times = 0
    
    while market_maker.done_amount['dui_qiao'] < target_amount:  # Loop until the target transaction volume is reached
        Log(market_maker.traded_pairs['dui_qiao'])
        market_maker.check_order_status(check_times)  # Check order status
        Sleep(1000)  # Wait 1 second
        market_maker.refresh_data()  # Refresh data
        
        if len(market_maker.traded_pairs['dui_qiao']) < 1: # Price moves, market orders are cancelled, wait until all orders are completed, and create a new order dictionary
            
            Log('2 The number of trading pairs on the market is less than 1')
            trade_dict = market_maker.make_duiqiao_dict(trade_amount)  # Generate a dictionary of pending orders
            Log('New trading dictionary', trade_dict)
        
        if trade_dict:  # Check if the dictionary is not empty
            market_maker.make_trade_by_dict(trade_dict)  # Execute a trade

        Log('Market making quantity:', market_maker.done_amount['dui_qiao'])  # Record transaction volume

        market_maker.plot_pending()
        market_maker.update_status()

        check_times += 1
        
    Log(market_maker.position_amount, market_maker.can_buy_amount)  # Record holdings and available purchase quantities
    Log('Existing orders:', exchange.GetOrders())  # Record existing orders

def onexit():
    Log("Execute the sweep function")

    _G("init_balance", None)
    _G("init_stocks", None)
    _G("init_equity", None)

From: An Overview of Market Making Strategies in Crypto: Architecture Design and FMZ Implementation of the Self-Matching Trading Strategy