2
フォロー
319
フォロワー

デジタル通貨のマーケットメイク戦略に関する簡単な議論:ノックオン戦略のアーキテクチャ設計とFMZプラットフォームの実装

作成日:: 2025-07-24 11:03:59, 更新日:: 2025-07-28 13:06:13
comments   0
hits   622

⚠️重要なお知らせ

この記事では、取引の枠組みを学ぶための、取引量増加戦略を紹介します。これは、従来の裁定取引によるマーケットメイク戦略とは根本的に異なります。この戦略の主な目的は、価格差による裁定取引で利益を得ることではなく、同じ価格で売買することで取引量を増やし、取引所のリベートやレベルディスカウントを獲得することです。

本記事で提供されるコードはリファレンスフレームワークであり、リアルタイム運用の経験はありません。本記事の戦略実装は技術学習および研究目的のみであり、実際の市場環境で十分に検証されていません。読者が本記事の内容を参照する場合は、十分なバックテスト検証とリスク評価を実施し、リアルタイム取引に直接使用しないでください。


デジタル通貨市場において、マーケットメイク戦略は市場流動性の向上と取引促進のためのツールであるだけでなく、多くの定量取引戦略の重要な要素でもあります。マーケットメーカーは、売買価格を提示し流動性を提供することで、様々な市場環境で利益を上げます。プロのマーケットメーカーのコード実装は、高頻度遅延最適化、複雑なリスク管理システム、複数取引所での裁定取引といった高度な機能を必要とするため、非常に複雑になることがよくあります。今回は、ブラシボリューム逆張り戦略の基本的な考え方と、Inventor Quantitative(FMZ)プラットフォーム上で簡略化された学習フレームワークを実装する方法を学びます。

この記事の本文は、原著者Zinan氏の「マーケットメイク戦略の考え方と記述法」に基づいています。一部はfmzプラットフォーム上で最適化・再現されています。現代の観点から見ると、一部の記述法は時代遅れかもしれませんが、それでもなお、コード構造とカウンタートレードの基本的な考え方を理解する上で、皆様にとって刺激的な内容となっています。

マーケットメイク戦略の概念

マーケットメイク戦略とは、トレーダー(マーケットメーカー)が市場に同時に売買注文を出すことで流動性を提供し、市場の安定性を維持することを指します。この戦略は市場の厚みを維持するだけでなく、他のトレーダーに取引相手を提供することにもつながります。マーケットメーカーは、異なる価格帯で売買の見積もりを提示することで、価格変動から利益を得ます。

マーケットメーカーの役割は、暗号通貨市場、特に取引量が少なくボラティリティが高い市場において極めて重要です。マーケットメーカーは流動性を提供することで、市場のスリッページを軽減し、トレーダーにとって取引しやすい価格を提供します。

伝統的なマーケットメイク戦略の基本原則は、流動性を提供することで売買スプレッドを獲得することです。マーケットメーカーが提示する買い注文価格は売り注文価格よりも低く、取引スプレッドを通じて利益を得ます。例えば、市場のスポット価格が上昇した場合、マーケットメーカーは高い価格で売り、低い価格で買い、スプレッドを獲得します。主な収益源は以下のとおりです。

  • 広める:伝統的なマーケットメーカーは、売買注文を出し、価格差を利用することで利益を上げます。
  • 取引量収益:マーケットメーカーの収入は、提供する取引量と密接に関連しています。取引量が多いほど、取引頻度が高まり、利益獲得の機会が増えるだけでなく、以下のような追加のメリットももたらします。
    • 手数料割引:マーケットメーカーが流動性を提供するよう促すため、多くの取引所は手数料の一定割合を払い戻したり、マイナス手数料(つまり、取引所がマーケットメーカーに手数料を支払う)を提供したりします。
    • VIPレベルの特典:取引量が一定の閾値に達すると、マーケットメーカーは手数料を安く抑え、取引コストを削減できる。
    • マーケットメーカーインセンティブプログラム一部の取引所では、提供される流動性の質に基づいて追加の報酬を与える専用のマーケットメーカーインセンティブプログラムを提供しています。

しかし、マーケットメーカーは、特にデジタル通貨市場のボラティリティが非常に高い環境においては、市場のボラティリティリスクにも直面しています。市場の急激な変動により、マーケットメーカーが発注する売買注文が実際の価格から大きく乖離し、損失が発生する可能性があります。

マーケットメイク戦略の種類

暗号通貨市場では、マーケットメーカーは通常、市場の状況、取引量、ボラティリティなどに基づいてさまざまなマーケットメイク戦略を選択します。一般的なマーケットメイク戦略には、次のようなものがあります。

  • パッシブマーケットメイク戦略:マーケットメーカーは、市場の厚み、過去のボラティリティなどに基づいて売買注文を出し、市場取引を待ちます。この戦略は、低頻度で堅調な取引を特徴としており、マーケットメーカーは市場の変動に依存して利益を上げます。

  • アクティブマーケットメイク戦略この戦略では、マーケットメーカーは市場の状況に応じて売買注文の価格と数量をリアルタイムで調整し、約定確率を高めます。マーケットメーカーは通常、価格が現在の市場価格に近いときに注文数を増やし、市場の変動をより有効に活用します。

  • ボリュームブースト戦略: この記事で焦点を当てる戦略の種類。ボリュームブースト戦略同じ価格で売買することで取引量を増やす戦略です。従来のマーケットメイク戦略とは異なり、この戦略の主な目的は価格差を稼ぐことではなく、多数の取引を通じて取引所のリベート、レベル割引、または流動性マイニング報酬を獲得することです。

ボリュームウォッシング戦略では、マーケットメーカーは売買注文を同じ価格で出します。注文が約定すると、価格差による利益は発生しませんが、取引量は急速に蓄積されます。この戦略の収益モデルは、市場裁定ではなく、取引所のインセンティブメカニズムに完全に依存しています。

主な機能:

  • 同価格取引:従来のマーケットメイク戦略とは異なり、買値と売値は同じであり、価格差利益は発生しません。

  • ボリューム重視:この戦略の中心的な目標は、価格裁定取引ではなく、取引量を迅速に蓄積することです。

  • インセンティブへの依存:利益は取引所のリベートポリシー、VIP レベルの割引、またはマーケットメーカーのインセンティブ プログラムによって完全に左右されます。

重要な違い: 伝統的なマーケットメイク戦略と比較すると、ウォッシュトレーディング戦略は、実質的な市場流動性を提供することで利益を得るのではなく、取引所から政策上の利益を得るために人為的に取引量を作り出すことで利益を得ます。この戦略は、一部の法域ではコンプライアンスリスクに直面する可能性があり、実際に適用する際には慎重に評価する必要があります。

ボリュームベースの反対売買戦略の利益ロジック

コードを分析すると、この戦略における買値と売値がまったく同じであることがわかります。

def make_duiqiao_dict(self, trade_amount):
    mid_price = self.mid_price  # 中间价
    trade_price = round(mid_price, self.price_precision)  # 精准交易价格
    trade_dict = {
        'trade_price': trade_price,  # 买卖都使用同一个价格
        'amount': trade_amount
    }
    return trade_dict

実際の利益ロジック

1. 取引量ブラッシング戦略

  • この戦略の主な目的は、多数の取引を通じて取引量を増やすことです。
  • 取引所のリベート、ティア割引、流動性マイニング報酬を獲得
  • マーケットメーカーインセンティブプログラムを実施している取引所に適用

2. 手数料返金の仕組み

  • 取引所のマイナス手数料ポリシーによって異なります(メーカーレートはマイナスです)
  • 流動性を提供することで手数料の割引を受ける
  • 取引所はマーケットメーカー優遇レートをサポートする必要がある

適用可能なシナリオとリスク

✅ 適用可能なシナリオ

  • 取引所はマーケットメーカーに対して明確な優遇政策を持っている
  • より高い取引量要件を伴うVIPレベルのアップグレードに適しています
  • 流動性マイニングやリベート活動を行うプラットフォーム

❌ 該当なし

  • リベート制度のない取引所
  • 取引手数料が高いプラットフォーム
  • ウォッシュトレードに明確な制限を設けている取引所

⚠️ リスクリマインダー

  • 買い注文と売り注文が同時に執行された場合、通常は手数料を差し引いた後に損失が発生します。
  • 為替政策が変更された場合、戦略は無効になる可能性がある
  • 手数料コストの継続的な監視が必要
  • コンプライアンスリスクに直面する可能性があります(一部の地域ではブラッシング行動に制限があります)

ノックオン戦略フレームワークの分析

この記事では、Zinan氏のコードフレームワークを参考に、取引所環境において同一価格売買戦略を通じて取引量を積み上げる方法に焦点を当て、シンプルな出来高増加戦略の実装を紹介します。この戦略フレームワークは、主に2つのクラスで構成されています。MidClass そして MarketMakerこれら 2 つのクラスは、取引所の中間層の相互作用とノックオン戦略の具体的な実行を担当します。

この戦略アーキテクチャは、取引所インターフェースとマーケットメイク戦略を分離する階層型設計を採用しており、システムの優れた拡張性と柔軟性を確保しています。アーキテクチャの主なコンポーネントは以下のとおりです。

  1. MidClass:取引所中間層は、取引所インターフェースと対話して市場データ、口座情報、注文状況などを取得する役割を担います。この層は、外部取引所とのすべての対話をカプセル化し、取引ロジックと取引所インターフェースの分離を保証します。
  2. MarketMaker:マーケットメイク戦略クラス。ノックツートレード戦略の実行、保留注文の生成、注文ステータスの確認、戦略ステータスの更新などを担当します。取引所の中間層と連携して、具体的なマーケットメイクとノックツートレード操作を提供します。

MidClass

MidClass取引所の中間層として、その主な役割は取引所とのやり取りを処理し、すべての外部API呼び出しをカプセル化し、簡潔なインターフェースを提供することです。MarketMaker使用。そのアーキテクチャには、次の主要な機能が含まれています。

  1. 市場データの取得

    • 市場価格、深度、K ラインなどのリアルタイム データを市場から取得します。戦略の実行時にデータが最新であることを保証するために、市場データを定期的に更新する必要があります。
  2. アカウント情報管理

    • 口座残高、ポジション状況などの口座情報を取得します。これは資金管理とリスク管理に不可欠です。
  3. 注文管理

    • 注文の作成、照会、キャンセルのためのインターフェースを提供します。これはマーケットメイク戦略を実行するための基盤であり、市場における保留注文の生成と管理を可能にします。
  4. データの保存と更新

    • 戦略で使用するためにデータを継続的に更新するために取引所との接続を維持します。

これらの機能をカプセル化することでMidClass上記の例では、取引戦略クラス(MarketMaker取引所とのやり取りを気にすることなく、取引戦略の実行に集中できます。この構造により、システムの保守性と拡張性が向上し、異なる取引所へのサポートの追加や既存機能の最適化が容易になります。

MarketMaker

MarketMakerクロス取引戦略の中核クラスであり、マーケットメイク操作とクロス取引の実行を担います。そのアーキテクチャには、以下の主要モジュールが含まれています。

  1. 初期化

    • 交換中間層を初期化するMidClass、取引ペア、正確性、市場の深さなどの取引所の基本情報を入手できます。
    • マーケット メイク戦略を初期化し、保留注文の数、ビッド アスク スプレッドなどの必要なパラメータを設定します。これらのパラメータは、戦略の実行方法と効果に影響します。
  2. データ更新

    • リアルタイムのアカウント情報、市場価格、深度、K ラインなどの市場データを定期的に更新します。これらのデータは、戦略を実行するための基本情報を提供します。
    • 更新頻度は市場の変動に応じて調整でき、市場の変化にリアルタイムで対応できます。
  3. ノック戦略の実行

    • 保留注文の生成:市場の厚みと現在の価格変動に基づいて、保留注文辞書を生成します。保留注文辞書には、売買注文の価格と数量が含まれており、通常は戦略パラメータに基づいて自動的に計算されます。
    • ノックオン取引の実行:保留注文が生成されると、MarketMaker市場に提出され、売買注文が同時に執行されます。同じ価格で売買することで、取引量を迅速に積み上げることが目的です。
    • 注文状況の確認:ノックオン取引を実行する場合、MarketMaker保留中の注文が時間どおりに処理されるよう、注文のステータスを常に確認します。注文が約定しない場合は、注文が成立するまで保留中の注文の価格または数量を調整します。
  4. ステータスアップデート

    • ポリシーステータスの更新:取引量、完了注文、手数料などを含む戦略のステータスを定期的に更新します。このステータス情報を通じて、ユーザーは戦略のパフォーマンスをリアルタイムで監視できます。
    • リスク管理ノックオン戦略は、さまざまな市場環境で実行する必要があります。MarketMaker戦略実行方法は、さまざまな市場環境に適応するために動的に調整されます。

ノックオン戦略のロジックの再現

クロス取引戦略の実装は、正確な市場データと迅速な実行に依存します。MarketMakerリアルタイムの市場状況を監視し、反対注文(同じ価格での売買)の手法を活用することで、取引量を急速に蓄積し、戦略的な目標を達成することができます。

初期化

存在する MarketMakerクラス初期化メソッドでは、まず取引所の精度情報を取得し、取引量の精度、価格の精度などの戦略パラメータを初期化します。

self.precision_info = self.exchange_mid.get_precision()  # 获取精度信息
self.price_precision = self.precision_info['price_precision']  # 价格精度
self.amount_precision = self.precision_info['amount_precision']  # 交易量精度

ノックオン注文の辞書を生成する

クロス取引戦略の中核は、買値と売値、数量を含むクロス取引注文の辞書を生成することです。このコードは、仲値を計算することを通じてクロス取引注文の辞書を生成します。

def make_duiqiao_dict(self, trade_amount):
    mid_price = self.mid_price  # 中间价
    trade_price = round(mid_price, self.price_precision)  # 精准交易价格
    trade_dict = {
        'trade_price': trade_price,
        'amount': trade_amount
    }
    return trade_dict

ノックオン取引を実行する

生成されたクロス取引注文の辞書に従って、クロス取引取引が実行されます。create_order買い注文と売り注文を同時に出す方法です。

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'])  # 挂买单
        sell_id = self.exchange_mid.create_order('sell', trade_dict['trade_price'], trade_dict['amount'])  # 挂卖单
        self.traded_pairs['dui_qiao'].append({
            'buy_id': buy_id, 'sell_id': sell_id, 'init_time': time.time(), 'amount': trade_dict['amount']
        })

注文状況を確認する

定期的に注文状況を確認し、未完了の注文を処理します。GetOrderメソッドは注文ステータスを取得し、そのステータスに基づいて注文をキャンセルするかどうかを決定します。ノック注文の処理ロジックは主に以下のステップで構成されます。

  1. 注文ステータスを取得する

    • 取引所インターフェースを通じて売買注文のステータスを取得します。
    • 注文ステータスの取得に失敗した場合(たとえば、注文が存在しない、ネットワークに問題があるなど)、注文はキャンセルされ、レコードから削除されます。
  2. 注文状況の判断

    • 注文のステータスに基づいて、注文が完了したか、部分的に完了したか、未完了かを判別します。
    • 注文ステータスには次のものが含まれます:
      • 0(ORDER_STATE_PENDING): 未完了(注文保留中)。
      • 1(ORDER_STATE_CLOSED): 完了しました(取引完了)。
      • 2(ORDER_STATE_CANCELED): 取り消されました。
      • 3(ORDER_STATE_UNKNOWN): ステータスは不明です。
  3. 注文状況の処理

    • 両方の注文が完了していません
      • 買い注文と売り注文の両方が完了していない場合(ステータスが0)、その後、投票時間に応じて(current_time % 5 == 0)が注文をキャンセルするかどうかを決定します。
      • 注文をキャンセルした後、保留中の注文数を更新し、レコードから注文を削除します。
    • 1つの注文は完了したが、もう1つの注文は完了していない
      • 注文が完了した場合(ステータスが1)、別の注文が完了していない(ステータスが0)、ポーリング時間に基づいて未完了の注文をキャンセルするかどうかを決定します。
      • オープン注文をキャンセルした後、数量とオープン注文のリストを更新し、レコードから注文を削除します。
    • 両方の注文が完了しました
      • 買い注文と売り注文の両方が完了した場合(ステータスが1)の場合、取引量が更新され、注文は記録から削除されます。
    • 注文状況不明
      • 注文ステータスがどちらでもない場合0あまり1不明なステータスとして記録され、ログに記録されます。
  4. 更新記録

    • 注文ステータス処理の結果に応じて、取引量、未完了注文リスト、保留注文数を更新します。

その後の戦略展望

この記事で紹介するボリュームブースティング戦略は、主に取引フレームワークのアーキテクチャ設計を学ぶためのものであり、実際の応用価値は限られています。読者が実際のマーケットメイク戦略にご興味をお持ちの場合は、後ほどより実践的な戦略内容をご紹介します。

1. マーケットメイク戦略

  • ビッド・アスク・スプレッドに基づく真の裁定取引戦略
  • 買値と売値の間に注文を出して価格差を稼ぐ
  • 伝統的なマーケットメーカーの収益モデルに沿う

2. ダイナミックマーケットメイク戦略

  • 市場のボラティリティに基づいて保留注文の価格を動的に調整します
  • 在庫管理とリスク管理の仕組みの導入
  • さまざまな市場環境に合わせた適応型マーケットメイキング

3. マルチレベルマーケットメイク戦略

  • 複数の価格レベルで同時に注文を出す
  • リスク分散による全体的なリターンの安定性の向上
  • プロのマーケットメーカーの実際の運用に近い

これらの戦略は、実際の利益ロジックとリスク管理に重点を置き、定量トレーダーにとってより価値のある参考資料を提供します。

戦略展望

ウォッシュトレード戦略は取引所のインセンティブポリシーに依存しており、ポリシーが変更された場合、戦略が無効になる可能性があります。そのため、戦略はポリシー変更に対応できる必要があります。例えば、手数料率を動的に監視したり、複数の利益モデルを導入して単一依存のリスクを軽減したりする必要があります。さらに、ウォッシュトレード戦略は市場操作とみなされ、規制リスクに直面する可能性があります。実際の運用においては、トレーダーは関連する法律や規制を注意深く監視し、戦略のコンプライアンスを確保し、規制問題による損失を回避する必要があります。

読者の皆様が、基本的なフレームワークを理解した上で、ご自身の取引コンセプトと市場理解に基づき、戦略をさらに最適化・改善していかれることを願っています。クオンツ取引の魅力は、継続的な学習、実践、そして改善にあります。皆様がクオンツ取引の道を着実に歩んでいくことをお祈り申し上げます。

戦略コード

import time, json

class MidClass:
    def __init__(self, exchange_instance):
        '''
        初始化交易所中间层
        
        Args:
            exchange_instance: FMZ的交易所结构
        '''
        self.init_timestamp = time.time()  # 记录初始化时间
        self.exchange = exchange_instance  # 保存交易所对象
        self.exchange_name = self.exchange.GetName()  # 获取交易所名称
        self.trading_pair = self.exchange.GetCurrency()  # 获取交易对名称(如 BTC_USDT)

    def get_precision(self):
        '''
        获取交易对的精度信息
        
        Returns:
            返回包含精度信息的字典,失败时返回 None
        '''
        symbol_code = self.exchange.GetCurrency()
        ticker = self.exchange.GetTicker(symbol_code)  # 回测系统需要
        exchange_info = self.exchange.GetMarkets()
        data = exchange_info.get(symbol_code)

        if not data:
            Log("获取市场信息失败", GetLastError())
            return None

        # 获取该交易对的精度信息
        self.precision_info = {
            'tick_size': data['TickSize'],                  # 价格精度
            'amount_size': data['AmountSize'],              # 数量精度
            'price_precision': data['PricePrecision'],      # 价格小数位精度
            'amount_precision': data['AmountPrecision'],    # 数量小数位精度
            'min_qty': data['MinQty'],                      # 最小下单数量
            'max_qty': data['MaxQty']                       # 最大下单数量
        }

        return self.precision_info

    def get_account(self):
        '''
        获取账户信息
        
        Returns:
            获取信息成功返回 True,获取信息失败返回 False
        '''

        self.balance = '---'  # 账户余额
        self.amount = '---'  # 账户持仓量
        self.frozen_balance = '---'  # 冻结余额
        self.frozen_stocks = '---'  # 冻结持仓量
        self.init_balance = None
        self.init_stocks = None
        self.init_equity = None

        try:
            account_info = self.exchange.GetAccount()  # 获取账户信息
            self.balance = account_info['Balance']  # 更新账户余额
            self.amount = account_info['Stocks']  # 更新持仓量
            self.frozen_balance = account_info['FrozenBalance']  # 更新冻结余额
            self.frozen_stocks = account_info['FrozenStocks']  # 更新冻结持仓量
            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('获取初始eqity', 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  # 获取账户信息失败

    def get_ticker(self):
        '''
        获取市价信息(如买一价、卖一价、最高价、最低价等)
        
        Returns:
            获取信息成功返回 True,获取信息失败返回 False
        '''
        self.high_price = '---'  # 最高价
        self.low_price = '---'  # 最低价
        self.sell_price = '---'  # 卖一价
        self.buy_price = '---'  # 买一价
        self.last_price = '---'  # 最新成交价
        self.volume = '---'  # 成交量
        
        try:
            ticker_info = self.exchange.GetTicker()  # 获取市价信息
        
            self.high_price = ticker_info['High']  # 更新最高价
            self.low_price = ticker_info['Low']  # 更新最低价
            self.sell_price = ticker_info['Sell']  # 更新卖一价
            self.buy_price = ticker_info['Buy']  # 更新买一价
            self.last_price = ticker_info['Last']  # 更新最新成交价
            self.volume = ticker_info['Volume']  # 更新成交量
            return True
        except:
            return False  # 获取市价信息失败
        
    def get_depth(self):
        '''
        获取深度信息(买卖盘的挂单列表)
        
        Returns:
            获取信息成功返回 True,获取信息失败返回 False
        '''
        self.ask_orders = '---'  # 卖盘挂单列表
        self.bid_orders = '---'  # 买盘挂单列表
        
        try:
            depth_info = self.exchange.GetDepth()  # 获取深度信息
            self.ask_orders = depth_info['Asks']  # 更新卖盘挂单列表
            self.bid_orders = depth_info['Bids']  # 更新买盘挂单列表
            return True
        except:
            return False  # 获取深度信息失败
        
    def get_ohlc_data(self, period=PERIOD_M5):
        '''
        获取K线信息
        
        Args:
            period: K线周期,PERIOD_M1 指1分钟, PERIOD_M5 指5分钟, PERIOD_M15 指15分钟,
            PERIOD_M30 指30分钟, PERIOD_H1 指1小时, PERIOD_D1 指一天。
        '''
        self.ohlc_data = self.exchange.GetRecords(period)  # 获取K线数据
        
    def create_order(self, order_type, price, amount):
        '''
        提交一个挂单信息
        
        Args:
            order_type:挂单类型,'buy'指挂买单,'sell'指挂卖单
            price:挂单价格
            amount:挂单数量
            
        Returns:
            挂单Id号,可用以取消挂单
        '''
        if order_type == 'buy':
            try:
                order_id = self.exchange.Buy(price, amount)  # 提交买单
            except:
                return False  # 买单提交失败
            
        elif order_type == 'sell':
            try:
                order_id = self.exchange.Sell(price, amount)  # 提交卖单
            except:
                return False  # 卖单提交失败
        
        return order_id  # 返回订单ID
    
    def get_orders(self):
        '''
        获取未完成的订单列表
        
        Returns:
            未完成的订单列表
        '''
        self.open_orders = self.exchange.GetOrders()  # 获取未完成订单
        return self.open_orders
    
    def cancel_order(self, order_id):
        '''
        取消一个挂单信息
        
        Args:
            order_id:希望取消的挂单ID号
            
        Returns:
            取消挂单成功返回 True,取消挂单失败返回 False
        '''
        return self.exchange.CancelOrder(order_id)  # 取消订单
        
    def refresh_data(self):
        '''
        刷新信息(账户、市价、深度、K线)
        
        Returns:
            刷新信息成功返回 'refresh_data_finish!' 否则返回相应刷新失败的信息提示
        '''

        if not self.get_ticker():  # 刷新市价信息
            return 'false_get_ticker'

        if not self.get_account():  # 刷新账户信息
            return 'false_get_account'
        
        if not self.get_depth():  # 刷新深度信息
            return 'false_get_depth'
        
        try:
            self.get_ohlc_data()  # 刷新K线信息
        except:
            return 'false_get_K_line_info'
        
        return 'refresh_data_finish!'  # 刷新成功

class MarketMaker:
    def __init__(self, mid_class):
        '''
        初始化做市策略
        
        Args:
            mid_class: 交易所中间层对象
        '''
        self.exchange_mid = mid_class  # 交易所中间层对象
        self.precision_info = self.exchange_mid.get_precision()  # 获取精度信息

        self.done_amount = {'dui_qiao': 0}  # 已完成交易量
        self.price_precision = self.precision_info['price_precision']  # 价格精度
        self.amount_precision = self.precision_info['amount_precision']  # 交易量精度
        
        self.traded_pairs = {'dui_qiao': []}  # 已挂单的交易对
        self.pending_orders = []  # 未完成的订单状态
        self.pending_order_count = 0  # 挂单次数

        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": "挂单数量"},
            "xAxis": {"type": "datetime"},
            "yAxis": {
                "title": {"text": "挂单数量"},
                "opposite": False
            },
            "series": [
                {"name": "挂单买量", "id": "挂单买量", "data": []},
                {"name": "挂单卖量", "id": "挂单卖量", "dashStyle": "shortdash", "data": []}
            ]
        }
    
    def refresh_data(self):
        '''
        刷新数据(账户、市价、深度、K线)
        '''
        self.exchange_mid.refresh_data()  # 刷新交易所数据
        self.position_amount = 0 if isinstance(self.exchange_mid.amount, str) else self.exchange_mid.amount  # 持仓量
        self.available_balance = 0 if isinstance(self.exchange_mid.balance, str) else self.exchange_mid.balance  # 账户余额
        Log('检查ticker', self.exchange_mid.buy_price)
        self.can_buy_amount = self.available_balance / float(self.exchange_mid.buy_price)  # 可买的数量
        self.mid_price = (self.exchange_mid.sell_price + self.exchange_mid.buy_price) / 2  # 中间价

    def make_duiqiao_dict(self, trade_amount):
        
        '''
        生成对敲挂单字典
        
        Args:
            trade_amount: 每次交易量
        
        Returns:
            对敲挂单字典列表
        '''
        Log('3制作对敲挂单字典')

        mid_price = self.mid_price  # 中间价

        trade_price = round(mid_price, self.price_precision)  # 精准交易价格

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

        Log('返回盘口挂单字典:', trade_dict)
        return trade_dict
    
    def make_trade_by_dict(self, trade_dict):
        '''
        根据交易字典执行交易
        
        Args:
            trade_dict: 交易字典
        '''
        Log('4按照字典开始交易')
        self.refresh_data()  # 刷新数据
        
        if trade_dict:
            Log('当前账户资金: 币数余额: ', self.position_amount, '资金余额: ', self.can_buy_amount)
            Log('检查开仓: 币数限制: ', self.position_amount > trade_dict['amount'], '资金限制: ', 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'])  # 挂买单
                sell_id = self.exchange_mid.create_order('sell', trade_dict['trade_price'], trade_dict['amount'])  # 挂卖单
                
                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()  # 更新上次交易时间
        
    def handle_pending_orders(self):
        '''
        处理未完成的订单
        '''
        pending_orders = self.exchange_mid.get_orders()  # 获取未完成订单
        if len(pending_orders) > 0:
            for order in pending_orders:
                self.exchange_mid.cancel_order(order['Id'])  # 取消未完成订单

    def check_order_status(self, current_time):
        '''
        检查订单状态
        current_time: 轮询检查次数
        '''
        Log('1开始订单信息检查')
        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('检查订单:', traded_pair['buy_id'], traded_pair['sell_id'])

            try:
                buy_order_status = self.exchange_mid.exchange.GetOrder(traded_pair['buy_id'])  # 获取买单状态
                sell_order_status = self.exchange_mid.exchange.GetOrder(traded_pair['sell_id'])  # 获取卖单状态
            except:
                Log(traded_pair, '取消')
                self.exchange_mid.cancel_order(traded_pair['buy_id'])  # 取消买单
                self.exchange_mid.cancel_order(traded_pair['sell_id'])  # 取消卖单
                self.traded_pairs['dui_qiao'].remove(traded_pair)  # 移除订单
                return

            Log('检查订单:', 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('检查挂单,取消挂单(两未完)', buy_order_status['Status'], sell_order_status['Status'], current_time % 5)
                    self.exchange_mid.cancel_order(traded_pair['buy_id'])  # 取消买单
                    self.exchange_mid.cancel_order(traded_pair['sell_id'])  # 取消卖单
                    self.pending_order_count += 1  # 挂单次数加1
                    self.traded_pairs['dui_qiao'].remove(traded_pair)  # 移除订单

            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('检查挂单,取消挂单(一未完)', buy_order_status['Status'], sell_order_status['Status'])
                    self.done_amount['dui_qiao'] += traded_pair['amount']  # 更新交易量
                    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('取消该买订单,增加未完成买列表', traded_pair['buy_id'])
                        self.exchange_mid.cancel_order(traded_pair['buy_id'])  # 取消买单
                        self.pending_orders.append(['buy', buy_order_status['Status']])  # 记录未完成订单
                        Log('清除前:', self.traded_pairs['dui_qiao'])
                        Log('清除id:', traded_pair)
                        self.traded_pairs['dui_qiao'].remove(traded_pair)  # 移除订单
                        Log('清除后:', 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('取消该卖订单,增加未完成卖列表', traded_pair['sell_id'])
                        self.exchange_mid.cancel_order(traded_pair['sell_id'])  # 取消卖单
                        self.pending_orders.append(['sell', sell_order_status['Status']])  # 记录未完成订单
                        Log('清除前:', self.traded_pairs['dui_qiao'])
                        Log('清除id:', traded_pair)
                        self.traded_pairs['dui_qiao'].remove(traded_pair)  # 移除订单
                        Log('清除后:', self.traded_pairs['dui_qiao'])
                
            elif [sell_order_status['Status'], buy_order_status['Status']] == [1, 1]:
                Log('两订单都已完成')
                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('完成状态:', buy_order_status['Status'], sell_order_status['Status'], traded_pair['amount'])
                self.done_amount['dui_qiao'] += 2 * traded_pair['amount']  # 更新交易量
                self.traded_pairs['dui_qiao'].remove(traded_pair)  # 移除订单
            else:
                Log('两订单处于未知状态:', buy_order_status, sell_order_status)
                Log('未知订单状态:', buy_order_status['Status'], sell_order_status['Status'])
                Log('未知订单信息:', traded_pair)
        
    def update_status(self):

        self.exchange_mid.refresh_data()

        table1 = {
            "type": "table",
            "title": "账户信息",
            "cols": [
                "初始资金", "现存资金", "对敲买入数量", "对敲卖出数量", "费率", "总收益", "收益率"
            ],
            "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"初始化时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.exchange_mid.init_timestamp))}\n",
            f"`{json.dumps(table1)}`\n",
            f"最后执行时间: {_D()}\n"
        )

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

    def plot_pending(self):
        
        Log('对敲挂单数量:', self.buy_pending, self.sell_pending)
        self.obj_chart = Chart(self.chart)
        now_time = int(time.time() * 1000)
        # 更新挂单买量数据
        self.obj_chart.add(0, [now_time, self.buy_pending])
        # 更新挂单卖量数据
        self.obj_chart.add(1, [now_time, self.sell_pending])


def main():
    '''
    主函数,运行做市策略
    '''
    exchange.IO('simulate', True)
    exchange.IO("trade_super_margin")
    
    target_amount = 1  # 目标交易量
    trade_amount = 0.01  # 每次交易量
    trade_dict = {}  # 初始化交易字典
    
    exchange_mid = MidClass(exchange)  # 初始化交易所中间层
    Log(exchange_mid.refresh_data())  # 刷新数据
    market_maker = MarketMaker(exchange_mid)  # 初始化做市策略

    check_times = 0
    
    while market_maker.done_amount['dui_qiao'] < target_amount:  # 循环直到完成目标交易量
        Log(market_maker.traded_pairs['dui_qiao'])
        market_maker.check_order_status(check_times)  # 检查订单状态
        Sleep(1000)  # 等待1秒
        market_maker.refresh_data()  # 刷新数据
        
        if len(market_maker.traded_pairs['dui_qiao']) < 1: # 价格移动,盘口挂单撤销,等待至所有挂单完毕,制定新的挂单字典
            
            Log('2盘口交易对数量小于1')
            trade_dict = market_maker.make_duiqiao_dict(trade_amount)  # 生成盘口挂单字典
            Log('新交易字典', trade_dict)
        
        if trade_dict:  # 判断字典是否非空
            market_maker.make_trade_by_dict(trade_dict)  # 执行交易

        Log('盘口做市数量:', market_maker.done_amount['dui_qiao'])  # 记录交易量

        market_maker.plot_pending()
        market_maker.update_status()

        check_times += 1
        
    Log(market_maker.position_amount, market_maker.can_buy_amount)  # 记录持仓量和可买数量
    Log('现存订单:', exchange.GetOrders())  # 记录现存订单

def onexit():
    Log("执行扫尾函数")

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