हम एफएमजेड क्वांट ट्रेडिंग प्लेटफॉर्म कई क्रिप्टोक्यूरेंसी एक्सचेंजों का समर्थन करता है और बाजार पर मुख्यधारा के एक्सचेंजों को कैप्सूल करता है। हालांकि, अभी भी कई एक्सचेंज हैं जो कैप्सूल नहीं हैं। उन उपयोगकर्ताओं के लिए जिन्हें इन एक्सचेंजों का उपयोग करने की आवश्यकता है, वे उन्हें एफएमजेड क्वांट कस्टम प्रोटोकॉल के माध्यम से एक्सेस कर सकते हैं। न केवल क्रिप्टोक्यूरेंसी एक्सचेंजों तक सीमित है, कोई भी मंच जो समर्थन करता हैRESTप्रोटोकॉल याफिक्सप्रोटोकॉल भी एक्सेस किया जा सकता है।
इस लेख मेंRESTएक उदाहरण के रूप में एफएमजेड क्वांट ट्रेडिंग प्लेटफॉर्म के कस्टम प्रोटोकॉल का उपयोग करके ओकेएक्स एक्सचेंज के एपीआई को कैप्सूल करने और एक्सेस करने के तरीके को समझाने के लिए। जब तक अन्यथा निर्दिष्ट नहीं किया जाता, यह लेख आरईएसटी कस्टम प्रोटोकॉल को संदर्भित करता है।
एफएमजेड क्वांट ट्रेडिंग प्लेटफॉर्म पर एक्सचेंज को कॉन्फ़िगर करने के लिए पृष्ठः
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) प्लेटफ़ॉर्म एपीआई फ़ंक्शन को रणनीति में बुलाया जाता है, तो कस्टम प्रोटोकॉल प्रोग्राम डॉकर से अनुरोध प्राप्त करेगा। आप इसे प्लेटफ़ॉर्म के डिबगिंग टूल का उपयोग करके भी परीक्षण कर सकते हैं, उदाहरण के लिएः
डिबगिंग उपकरण पृष्ठः
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"}
.जब कस्टम प्रोटोकॉल प्रोग्राम डॉकर से अनुरोध प्राप्त करता है, तो यह अनुरोध में ले जाने वाली जानकारी के आधार पर रणनीति द्वारा अनुरोधित प्लेटफॉर्म एपीआई फ़ंक्शन (पैरामीटर जानकारी सहित) जैसी जानकारी प्राप्त कर सकता है।
इस सूचना के आधार पर, कस्टम प्रोटोकॉल प्रोग्राम आवश्यक डेटा प्राप्त करने या कुछ संचालन करने के लिए एक्सचेंज इंटरफ़ेस तक पहुँच सकता है।
आम तौर पर एक्सचेंज इंटरफेस में GET/POST/PUT/DELETE जैसे तरीके होते हैं, जो सार्वजनिक इंटरफेस और निजी इंटरफेस में विभाजित होते हैं।
कस्टम प्रोटोकॉल प्रोग्राम एक्सचेंज इंटरफेस से प्रतिक्रिया डेटा प्राप्त करता है और डॉकर द्वारा अपेक्षित डेटा (नीचे वर्णित) का निर्माण करने के लिए इसे आगे संसाधित करता है।GetTicker
, GetAccount
और अन्य कार्यों में CustomProtocolOKX वर्ग कार्यान्वयन में पायथन कस्टम प्रोटोकॉल उदाहरण में.
जब कस्टम प्रोटोकॉल प्रोग्राम एक्सचेंज के एपीआई इंटरफेस तक पहुँचता है, कुछ ऑपरेशन करता है या कुछ डेटा प्राप्त करता है, तो उसे डॉकर को परिणाम वापस फ़ीड करने की आवश्यकता होती है।
डॉकर को वापस खिलाए जाने वाले डेटा रणनीति द्वारा बुलाए गए इंटरफ़ेस के अनुसार भिन्न होते हैं, और पहले दो श्रेणियों में विभाजित होते हैंः
{
"data": null, // "data" can be of any type
"raw": null // "raw" can be of any type
}
डेटाः इस क्षेत्र की विशिष्ट संरचनाmethod
कस्टम प्रोटोकॉल प्रोग्राम द्वारा प्राप्त अनुरोध में, और एफएमजेड प्लेटफॉर्म एपीआई फ़ंक्शन द्वारा अंततः लौटाए गए डेटा संरचना का निर्माण करने के लिए उपयोग किया जाता है। सभी इंटरफेस नीचे सूचीबद्ध किए जाएंगे।
raw: इस क्षेत्र का उपयोग एक्सचेंज एपीआई इंटरफ़ेस प्रतिक्रिया के कच्चे डेटा में पारित करने के लिए किया जा सकता है, जैसे कि टिकर संरचना द्वारा लौटाया गयाexchange.GetTicker()
टिकर संरचना के सूचना क्षेत्र मेंraw
क्षेत्र औरdata
कुछ प्लेटफार्मों के एपीआई कार्यों को इन आंकड़ों की आवश्यकता नहीं होती है।
{
"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"))
}
उपरोक्त कस्टम प्रोटोकॉल प्रोग्राम की एक संक्षिप्त प्रक्रिया है जो एक्सचेंज एपीआई (एफएमजेड अनपैक किए गए) तक पहुंच में भाग लेती है। यह प्रक्रिया केवल प्रक्रिया को समझाती है जब कॉल किया जाता हैexchange.GetTicker()
(एफएमजेड) प्लेटफार्म डिबगिंग टूल में फ़ंक्शन। निम्नलिखित सभी प्लेटफार्म एपीआई फ़ंक्शंस के इंटरैक्शन विवरण को विस्तार से समझाएगा।
प्लेटफ़ॉर्म विभिन्न एक्सचेंजों के सामान्य कार्यों को कैप्सूल करता है और उन्हें एक निश्चित फ़ंक्शन में एकीकृत करता है, जैसे कि गेटटिकर फ़ंक्शन, जो एक निश्चित उत्पाद की वर्तमान बाजार जानकारी का अनुरोध करता है। यह एक एपीआई है जो मूल रूप से सभी एक्सचेंजों के पास है। इसलिए, रणनीति उदाहरण में प्लेटफ़ॉर्म द्वारा कैप्सूल किए गए एपीआई इंटरफ़ेस तक पहुंचने पर, डॉकर
POST /OKX HTTP/1.1
{
"access_key": "xxx",
"method": "ticker",
"nonce": 1730275031047002000,
"params": {"symbol":"LTC_USDT"},
"secret_key": "xxx"
}
रणनीति (जैसे GetTicker) में अलग-अलग FMZ प्लेटफ़ॉर्म कैप्सुलेट एपीआई फ़ंक्शन को कॉल करते समय, कस्टम प्रोटोकॉल को डॉकर द्वारा भेजे गए अनुरोध प्रारूप भी अलग होंगे। शरीर में डेटा (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()
और अन्य कार्यों को कैप्सुलेट करने की आवश्यकता नहीं है, और कॉल विधि और कार्य अपरिवर्तित रहते हैं।स्पॉट एक्सचेंजों के सभी कार्यों का समर्थन करने के अलावा, वायदा एक्सचेंजों में कुछ एपीआई कार्य भी हैं जो वायदा एक्सचेंजों के लिए अद्वितीय हैं।
लागू किया जाना है
आरईएसटी कस्टम प्रोटोकॉल - ओकेएक्स एक्सचेंज आरईएसटी एपीआई इंटरफेस तक पहुंच, स्पॉट एक्सचेंज ऑब्जेक्ट के रूप में कैप्सुलेट। एक सार्वजनिक इंटरफ़ेस अनुरोध और प्रतिक्रिया डेटा encapsulation लागू किया। एक निजी इंटरफ़ेस हस्ताक्षर, अनुरोध और प्रतिक्रिया डेटा कैप्सुलेशन लागू किया। यह उदाहरण मुख्य रूप से परीक्षण और सीखने के लिए है। अन्य इंटरफ़ेस परीक्षण के लिए डॉकर को सीधे प्रतिक्रिया देने के लिए अनुकरणीय डेटा का उपयोग करते हैं।
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()