We FMZ Quant Trading Platformは,多くの仮想通貨取引所をサポートし,市場の主流取引所をカプセル化しています.しかし,まだカプセル化されていない多くの取引所があります.これらの取引所を使用する必要があるユーザーには,FMZ Quant Custom Protocolを通じてアクセスできます.仮想通貨取引所に限らず,REST議定書またはFIXプロトコルもアクセスできます
この 記事 はREST標準化プロトコルは,FMZ Quant Trading Platformのカスタムプロトコルを使用してOKX取引所のAPIをカプセル化し,アクセスする方法を説明する例として使用する.別の規定がない限り,この記事はRESTカスタムプロトコルを参照します.
FMZ 量子取引プラットフォームの取引所の設定ページ:
https://www.fmz.com/m/platforms/add
Strategy instance running on the docker -> Custom protocol program
- わかった
例えば:http://127.0.0.1:6666/OKX
,カスタムプロトコルプログラムとドーカーは通常同じデバイス (サーバー) で実行されるため,サービスアドレスはローカルマシン (localhost) として書き込まれ,ポートはシステムに占用されていないポートとして使用できます.Strategy instance running on the docker -> Custom protocol program
.Strategy instance running on the docker -> Custom protocol program
.OKXプラグインの設定のスクリーンショットは以下のとおりです.
OKX交換秘密鍵の設定情報:
accessKey: accesskey123 // accesskey123, these are not actual keys, just for demonstration
secretKey: secretkey123
passphrase: passphrase123
http://127.0.0.1:6666
特定の経路を処理することができます./OKX
.戦略で (FMZ) プラットフォーム API 機能が呼び出されると,カスタム プロトコル プログラムはドッカーから要求を受け取ります.また,プラットフォームのデバッグ ツールを使用してテストすることもできます.例えば:
デバッグツールページ:
function main() {
return exchange.GetTicker("LTC_USDT")
}
関数を呼び出すexchange.GetTicker()
,カスタムプロトコルプログラムは,この要求を受け取ります:
POST /OKX HTTP/1.1
{
"access_key":"xxx",
"method":"ticker",
"nonce":1730275031047002000,
"params":{"symbol":"LTC_USDT"},
"secret_key":"xxx"
}
exchange.GetTicker()
, method
はticker
.exchange.GetTicker()
関連パラメータは:{"symbol":"LTC_USDT"}
.カスタムプロトコルプログラムがドッカーからのリクエストを受信すると,リクエストに含まれる情報に基づいて,戦略が要求するプラットフォーム API 機能 (パラメータ情報を含む) や交換鍵などの情報を取得することができます.
この情報に基づいて,カスタムプロトコルプログラムは,必要なデータを取得または特定の操作を実行するために交換インターフェースにアクセスできます.
通常,交換インターフェイスには,公開インターフェイスとプライベートインターフェイスに分かれているGET/POST/PUT/DELETEなどのメソッドがあります.
カスタムプロトコルプログラムは,交換インターフェイスから応答データを受信し,ドッカーが期待するデータを構築するためにそれを処理します. OKXスポット交換を参照してください.GetTicker
, GetAccount
Pythonのカスタムプロトコル例の CustomProtocolOKX クラス実装の他の関数.
カスタムプロトコルプログラムが Exchange の API インターフェースにアクセスし,特定の操作を実行したり,特定のデータを取得するとき,結果をドッカーにフィードバックする必要があります.
戦略によって呼び出されたインターフェースに応じて,ドッカーに返信されるデータは異なる.
{
"data": null, // "data" can be of any type
"raw": null // "raw" can be of any type
}
このフィールドの特異的な構造はmethod
FMZ プラットフォーム API 機能によって最終的に返されるデータ構造を構築するために使用される.すべてのインターフェースは以下にリストされます.
raw: このフィールドは,交換 API インタフェース応答の生データ,例えば,TCKER 構造が返されるデータを通すのに使用できます.exchange.GetTicker()
機能. ティッカーの構造のInfoフィールドは,raw
フィールドとdata
プラットフォームの API 機能のいくつかはこのデータを必要としません.
{
"error": "" // "error" contains an error message as a string
}
エラー: エラー ログに表示される エラー 情報は, (FMZ) プラットフォームのライブ取引,デバッグ ツール,その他のページのログ エリアに表示されます.
戦略プログラムによって受信されたカスタムプロトコル応答データを示します.
// Tested in the debugging tool of the FMZ platform
function main() {
Log(exchange.GetTicker("USDT")) // The trading pair is incomplete, the BaseCurrency part is missing, and the custom protocol plug-in is required to return an error message: {"error": "..."}
Log(exchange.GetTicker("LTC_USDT"))
}
上記は, (FMZ unpackaged) 交換 API にアクセスするカスタムプロトコル プログラムの短いプロセスです.このプロセスは,ただ,呼び出すときのプロセスを説明しますexchange.GetTicker()
(FMZ) プラットフォームデバッグツールで機能します.以下は,すべてのプラットフォーム API 機能の相互作用の詳細を詳細に説明します.
このプラットフォームは,さまざまな取引所の共通の機能をカプセル化し,特定の製品の現在の市場情報を要求するGetTicker機能などの特定の機能に統一します.これは基本的にすべての取引所が持っているAPIです.したがって,戦略インスタンスのプラットフォームによってカプセル化されたAPIインターフェースにアクセスする際に,ドッカーが
POST /OKX HTTP/1.1
{
"access_key": "xxx",
"method": "ticker",
"nonce": 1730275031047002000,
"params": {"symbol":"LTC_USDT"},
"secret_key": "xxx"
}
戦略 (GetTicker など) で異なる FMZ プラットフォームがカプセル化された API 関数を呼び出すとき,ドッカーがカスタム プロトコルに送信するリクエスト フォーマットも異なる.ボディ内のデータ (JSON) は,method
そしてparams
. カスタムプロトコルを設計するとき,メソッドの内容に応じて特定の操作を実行します. 以下はすべてのインターフェースのリクエスト応答シナリオです.
例えば,現在の取引ペアは,ETH_USDT
ドッカーがカスタムプロトコルの応答を期待するデータは主にデータフィールドに書き込まれ,交換インターフェースの元のデータを記録するために生フィールドも追加できます.
方法フィールド:
{"symbol":"ETH_USDT"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": {
"symbol": "ETH_USDT", // Corresponds to the Symbol field in the Ticker structure returned by the GetTicker function
"buy": "2922.18", // ...corresponds to the Buy field
"sell": "2922.19",
"high": "2955",
"low": "2775.15",
"open": "2787.72",
"last": "2922.18",
"vol": "249400.888156",
"time": "1731028903911"
},
"raw": {} // A raw field can be added to record the raw data of the exchange API interface response
}
方法フィールド:
{"limit":"30","symbol":"ETH_USDT"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data" : {
"time" : 1500793319499,
"asks" : [
[1000, 0.5], [1001, 0.23], [1004, 2.1]
// ...
],
"bids" : [
[999, 0.25], [998, 0.8], [995, 1.4]
// ...
]
}
}
方法欄: 取引 パラームフィールド:
{"symbol":"eth_usdt"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": [
{
"id": 12232153,
"time" : 1529919412968,
"price": 1000,
"amount": 0.5,
"type": "buy", // "buy"、"sell"、"bid"、"ask"
}, {
"id": 12545664,
"time" : 1529919412900,
"price": 1001,
"amount": 1,
"type": "sell",
}
// ...
]
}
メソッドフィールド:
{
"limit":"500",
"period":"60", // 60 minutes
"symbol":"ETH_USDT"
}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": [
// "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5
[1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
[1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
// ...
]
}
方法フィールド:
{}
ドーカーがカスタムプロトコル応答で期待するデータ:
{}
方法フィールド:
{}
ドーカーがカスタムプロトコル応答で期待するデータ:
{}
方法フィールド:
{}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
方法フィールド:
{}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
方法フィールド:
{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": {
"id": "BTC-USDT,123456"
}
}
方法フィールド:
{"symbol":"ETH_USDT"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": [
{
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"avg_price": "1000",
"type": "buy", // "buy"、"sell"
"status": "pending", // "pending", "pre-submitted", "submitting", "submitted", "partial-filled"
},
// ...
]
}
方法フィールド:
{
"id":"ETH-USDT,123456", // Calling in the strategy: exchange.GetOrder("ETH-USDT,123456")
"symbol":"ETH_USDT"
}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": {
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT"
"amount": 0.15,
"price": 1002,
"status": "pending", // "pending", "pre-submitted", "submitting", "submitted", "partial-filled", "filled", "closed", "finished", "partial-canceled", "canceled"
"deal_amount": 0,
"type": "buy", // "buy"、"sell"
"avg_price": 0, // If the exchange does not provide it, it can be assigned a value of 0 during processing.
}
}
メソッドフィールド:
{"limit":0,"since":0,"symbol":"ETH_USDT"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": [
{
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"avg_price": 1000,
"type": "buy", // "buy"、"sell"
"status": "filled", // "filled"
},
// ...
]
}
メソッドフィールド:
{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": true // As long as there is no error field in the JSON, the order cancellation is considered successful by default.
}
についてexchange.IO交換インターフェースに直接アクセスするために使用されます.例えば,
GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDT
例えば
// Called in the strategy instance
exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
メソッドフィールド:"__api_/api/v5/trade/orders-pending"
方法フィールドは _ から始まる.アピ発症する.exchange.IO戦略インスタンスの関数呼び出し
パラームフィールド:
{"instId":"ETH-USDT","instType":"SPOT"} // instType=SPOT&instId=ETH-USDT encoded parameters will be restored to JSON
ドーカーがカスタムプロトコル応答で期待するデータ:
{
"data": {"code": "0", "data": [], "msg": ""} // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response
}
exchange.Go()
, exchange.GetRawJSON()
呼び出し方法と関数は変わらなくなりますスポット取引所のすべての機能をサポートするだけでなく,先物取引所は先物取引所に特有のAPI機能も備えています.
実施する
RESTカスタムプロトコル - スポット交換オブジェクトとしてカプセル化されたOKX交換REST APIインターフェイスへのアクセス. 公開インターフェースの要求と応答データエンカプスリングを実装した. プライベートインターフェースの署名,要求および応答データエンカプスリングを実装しました. この例は主にテストと学習のためのものです.他のインターフェースは,テストのためにドッカーに直接応答するためにシミュレーションされたデータを使用します.
import http.server
import socketserver
import json
import urllib.request
import urllib.error
import argparse
import ssl
import hmac
import hashlib
import base64
from datetime import datetime
ssl._create_default_https_context = ssl._create_unverified_context
class BaseProtocol:
ERR_NOT_SUPPORT = {"error": "not support"}
def __init__(self, apiBase, accessKey, secretKey):
self._apiBase = apiBase
self._accessKey = accessKey
self._secretKey = secretKey
def _httpRequest(self, method, path, query="", params={}, addHeaders={}):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6',
'Content-Type': 'application/json; charset=UTF-8'
}
# add headers
for key in addHeaders:
headers[key] = addHeaders[key]
if method == "GET":
url = f"{self._apiBase}{path}?{query}" if query != "" else f"{self._apiBase}{path}"
req = urllib.request.Request(url, method=method, headers=headers)
else:
url = f"{self._apiBase}{path}"
req = urllib.request.Request(url, json.dumps(params, separators=(',', ':')).encode('utf-8'), method=method, headers=headers)
print(f'send request by protocol: {self.exName}, req:', req.method, req.full_url, req.headers, req.data, "\n")
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
except json.JSONDecodeError:
data = {"error": "Invalid JSON response"}
except urllib.error.HTTPError as e:
data = {"error": f"HTTP error: {e.code}"}
except urllib.error.URLError as e:
data = {"error": f"URL error: {e.reason}"}
except Exception as e:
data = {"error": f"Exception occurred: {str(e)}"}
print(f'protocol response received: {self.exName}, resp:', data, "\n")
return data
def GetTickers(self):
return self.ERR_NOT_SUPPORT
def GetMarkets(self):
return self.ERR_NOT_SUPPORT
def GetTicker(self, symbol):
return self.ERR_NOT_SUPPORT
def GetDepth(self, symbol=""):
return self.ERR_NOT_SUPPORT
def GetTrades(self, symbol=""):
return self.ERR_NOT_SUPPORT
def GetRecords(self, symbol, period, limit):
return self.ERR_NOT_SUPPORT
def GetAssets(self):
return self.ERR_NOT_SUPPORT
def GetAccount(self):
return self.ERR_NOT_SUPPORT
def CreateOrder(self, symbol, side, price, amount):
return self.ERR_NOT_SUPPORT
def GetOrders(self, symbol=""):
return self.ERR_NOT_SUPPORT
def GetOrder(self, orderId):
return self.ERR_NOT_SUPPORT
def CancelOrder(self, orderId):
return self.ERR_NOT_SUPPORT
def GetHistoryOrders(self, symbol, since, limit):
return self.ERR_NOT_SUPPORT
def GetPostions(self, symbol=""):
return self.ERR_NOT_SUPPORT
def SetMarginLevel(self, symbol, marginLevel):
return self.ERR_NOT_SUPPORT
def GetFundings(self, symbol=""):
return self.ERR_NOT_SUPPORT
def IO(self, params):
return self.ERR_NOT_SUPPORT
class ProtocolFactory:
@staticmethod
def createExWrapper(apiBase, accessKey, secretKey, exName) -> BaseProtocol:
if exName == "OKX":
return CustomProtocolOKX(apiBase, accessKey, secretKey, exName)
else:
raise ValueError(f'Unknown exName: {exName}')
class CustomProtocolOKX(BaseProtocol):
"""
CustomProtocolOKX - OKX API Wrapper
# TODO: add information.
"""
def __init__(self, apiBase, accessKey, secretKey, exName):
secretKeyList = secretKey.split(",")
self.exName = exName
self._x_simulated_trading = 0
if len(secretKeyList) > 1:
self._passphrase = secretKeyList[1]
if len(secretKeyList) > 2:
if secretKeyList[2] == "simulate":
self._x_simulated_trading = 1
else:
raise ValueError(f"{self.exName}: invalid secretKey format.")
super().__init__(apiBase, accessKey, secretKeyList[0])
def getCurrencys(self, symbol):
baseCurrency, quoteCurrency = "", ""
arrCurrency = symbol.split("_")
if len(arrCurrency) == 2:
baseCurrency = arrCurrency[0]
quoteCurrency = arrCurrency[1]
return baseCurrency, quoteCurrency
def getSymbol(self, instrument):
arrCurrency = instrument.split("-")
if len(arrCurrency) == 2:
baseCurrency = arrCurrency[0]
quoteCurrency = arrCurrency[1]
else:
raise ValueError(f"{self.exName}: invalid instrument: {instrument}")
return f'{baseCurrency}_{quoteCurrency}'
def callUnsignedAPI(self, httpMethod, path, query="", params={}):
return self._httpRequest(httpMethod, path, query, params)
def callSignedAPI(self, httpMethod, path, query="", params={}):
strTime = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else ""
message = f'{strTime}{httpMethod}{path}{jsonStr}'
if httpMethod == "GET" and query != "":
message = f'{strTime}{httpMethod}{path}?{query}{jsonStr}'
mac = hmac.new(bytes(self._secretKey, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
signature = base64.b64encode(mac.digest())
headers = {}
if self._x_simulated_trading == 1:
headers["x-simulated-trading"] = str(self._x_simulated_trading)
headers["OK-ACCESS-KEY"] = self._accessKey
headers["OK-ACCESS-PASSPHRASE"] = self._passphrase
headers["OK-ACCESS-TIMESTAMP"] = strTime
headers["OK-ACCESS-SIGN"] = signature
return self._httpRequest(httpMethod, path, query, params, headers)
# Encapsulates requests to the exchange API.
def GetTicker(self, symbol):
"""
GET /api/v5/market/ticker , param: instId
"""
baseCurrency, quoteCurrency = self.getCurrencys(symbol)
if baseCurrency == "" or quoteCurrency == "":
return {"error": "invalid symbol"}
path = "/api/v5/market/ticker"
query = f'instId={baseCurrency}-{quoteCurrency}'
data = self.callUnsignedAPI("GET", path, query=query)
if "error" in data.keys() and "data" not in data.keys():
return data
ret_data = {}
if data["code"] != "0" or not isinstance(data["data"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for tick in data["data"]:
if not all(k in tick for k in ("instId", "bidPx", "askPx", "high24h", "low24h", "vol24h", "ts")):
return {"error": json.dumps(data, ensure_ascii=False)}
ret_data["symbol"] = self.getSymbol(tick["instId"])
ret_data["buy"] = tick["bidPx"]
ret_data["sell"] = tick["askPx"]
ret_data["high"] = tick["high24h"]
ret_data["low"] = tick["low24h"]
ret_data["open"] = tick["open24h"]
ret_data["last"] = tick["last"]
ret_data["vol"] = tick["vol24h"]
ret_data["time"] = tick["ts"]
return {"data": ret_data, "raw": data}
def GetDepth(self, symbol):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = {
"time" : 1500793319499,
"asks" : [
[1000, 0.5], [1001, 0.23], [1004, 2.1]
],
"bids" : [
[999, 0.25], [998, 0.8], [995, 1.4]
]
}
return {"data": ret_data}
def GetTrades(self, symbol):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
{
"id": 12232153,
"time" : 1529919412968,
"price": 1000,
"amount": 0.5,
"type": "buy",
}, {
"id": 12545664,
"time" : 1529919412900,
"price": 1001,
"amount": 1,
"type": "sell",
}
]
return {"data": ret_data}
def GetRecords(self, symbol, period, limit):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
[1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
[1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
]
return {"data": ret_data}
def GetMarkets(self):
"""
TODO: Implementation code
"""
ret_data = {}
return {"data": ret_data}
def GetTickers(self):
"""
TODO: Implementation code
"""
ret_data = {}
return {"data": ret_data}
def GetAccount(self):
"""
GET /api/v5/account/balance
"""
path = "/api/v5/account/balance"
data = self.callSignedAPI("GET", path)
ret_data = []
if data["code"] != "0" or "data" not in data or not isinstance(data["data"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for ele in data["data"]:
if "details" not in ele or not isinstance(ele["details"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for detail in ele["details"]:
asset = {"currency": detail["ccy"], "free": detail["availEq"], "frozen": detail["ordFrozen"]}
if detail["availEq"] == "":
asset["free"] = detail["availBal"]
ret_data.append(asset)
return {"data": ret_data, "raw": data}
def GetAssets(self):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}
]
return {"data": ret_data}
def CreateOrder(self, symbol, side, price, amount):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = {
"id": "BTC-USDT,123456"
}
return {"data": ret_data}
def GetOrders(self, symbol):
"""
GET /api/v5/trade/orders-pending instType SPOT instId after limit
"""
baseCurrency, quoteCurrency = self.getCurrencys(symbol)
if baseCurrency == "" or quoteCurrency == "":
return {"error": "invalid symbol"}
path = "/api/v5/trade/orders-pending"
after = ""
limit = 100
ret_data = []
while True:
query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}"
if after != "":
query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}&after={after}"
data = self.callSignedAPI("GET", path, query=query)
if data["code"] != "0" or not isinstance(data["data"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for ele in data["data"]:
order = {}
order["id"] = f'{ele["instId"]},{ele["ordId"]}'
order["symbol"] = f'{baseCurrency}-{quoteCurrency}'
order["amount"] = ele["sz"]
order["price"] = ele["px"]
order["deal_amount"] = ele["accFillSz"]
order["avg_price"] = 0 if ele["avgPx"] == "" else ele["avgPx"]
order["type"] = "buy" if ele["side"] == "buy" else "sell"
order["state"] = "pending"
ret_data.append(order)
after = ele["ordId"]
if len(data["data"]) < limit:
break
return {"data": ret_data}
def GetOrder(self, orderId):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = {
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.15,
"price": 1002,
"status": "pending",
"deal_amount": 0,
"type": "buy",
"avg_price": 0,
}
return {"data": ret_data}
def GetHistoryOrders(self, symbol, since, limit):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
{
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"avg_price": 1000,
"type": "buy",
"status": "filled"
}
]
return {"data": ret_data}
def CancelOrder(self, orderId):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = True
return {"data": ret_data}
def IO(self, httpMethod, path, params={}):
if httpMethod == "GET":
query = urllib.parse.urlencode(params)
data = self.callSignedAPI(httpMethod, path, query=query)
else:
data = self.callSignedAPI(httpMethod, path, params=params)
if data["code"] != "0":
return {"error": json.dumps(data, ensure_ascii=False)}
return {"data": data}
class HttpServer(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.request_body = None
self.request_path = None
super().__init__(*args, **kwargs)
def log_message(self, format, *args):
return
def _sendResponse(self, body):
self.send_response(200)
self.send_header('Content-type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(body).encode('utf-8'))
def do_GET(self):
# The FMZ.COM custom protocol only send GET method request
self._sendResponse({"error": "not support GET method."})
def do_POST(self):
"""
Returns:
json: success, {"data": ...}
json: error, {"error": ...}
"""
contentLen = int(self.headers['Content-Length'])
self.request_body = self.rfile.read(contentLen)
self.request_path = self.path
exName = self.request_path.lstrip("/")
# Print the request received from the FMZ.COM robot
print(f"--------- request received from the FMZ.COM robot: --------- \n {self.requestline} | Body: {self.request_body} | Headers: {self.headers} \n")
try:
data = json.loads(self.request_body)
except json.JSONDecodeError:
data = {"error": self.request_body.decode('utf-8')}
self._sendResponse(data)
return
# fault tolerant
if not all(k in data for k in ("access_key", "secret_key", "method", "params")):
data = {"error": "missing required parameters"}
self._sendResponse(data)
return
respData = {}
accessKey = data["access_key"]
secretKey = data["secret_key"]
method = data["method"]
params = data["params"]
exchange = ProtocolFactory.createExWrapper("https://www.okx.com", accessKey, secretKey, exName)
if method == "ticker":
symbol = str(params["symbol"]).upper()
respData = exchange.GetTicker(symbol)
elif method == "depth":
symbol = str(params["symbol"]).upper()
respData = exchange.GetDepth(symbol)
elif method == "trades":
symbol = str(params["symbol"]).upper()
respData = exchange.GetTrades(symbol)
elif method == "records":
symbol = str(params["symbol"]).upper()
period = int(params["period"])
limit = int(params["limit"])
respData = exchange.GetRecords(symbol, period, limit)
elif method == "accounts":
respData = exchange.GetAccount()
elif method == "assets":
respData = exchange.GetAssets()
elif method == "trade":
amount = float(params["amount"])
price = float(params["price"])
symbol = str(params["symbol"])
tradeType = str(params["type"])
respData = exchange.CreateOrder(symbol, tradeType, price, amount)
elif method == "orders":
symbol = str(params["symbol"]).upper()
respData = exchange.GetOrders(symbol)
elif method == "order":
orderId = str(params["id"])
respData = exchange.GetOrder(orderId)
elif method == "historyorders":
symbol = str(params["symbol"])
since = int(params["since"])
limit = int(params["limit"])
respData = exchange.GetHistoryOrders(symbol, since, limit)
elif method == "cancel":
orderId = str(params["id"])
respData = exchange.CancelOrder(orderId)
elif method[:6] == "__api_":
respData = exchange.IO(self.headers["Http-Method"], method[6:], params)
else:
respData = {"error": f'invalid method: {method}'}
# Print the response to send to FMZ.COM robot
print(f"response to send to FMZ.COM robot: {respData} \n")
self._sendResponse(respData)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run a FMZ.COM custom protocol plugin.")
parser.add_argument("--port", type=int, default=6666, help="Port to run the server on.")
parser.add_argument("--address", type=str, default="localhost", help="Address to bind the server to.")
args = parser.parse_args()
with socketserver.TCPServer((args.address, args.port), HttpServer) as httpd:
print(f"running... {args.address}:{args.port}", "\n")
httpd.serve_forever()