An Overview of Market Making Strategies in Crypto: Architecture Design and FMZ Implementation of the Self-Matching Trading Strategy
⚠️ 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:
- 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.
- 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 (status0
), 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)