[TOC]
موجد مقداری تجارتی پلیٹ فارم بہت سے کریپٹو کرنسی ایکسچینجز کو سپورٹ کرتا ہے اور مارکیٹ میں مرکزی دھارے کے تبادلے کو سمیٹتا ہے۔ تاہم، ابھی بھی بہت سے ایکسچینجز ہیں جو پیک نہیں کیے گئے ہیں، جن صارفین کو ان ایکسچینجز کو استعمال کرنے کی ضرورت ہے، وہ ان تک رسائی حاصل کر سکتے ہیں جو کہ موجد کے ذریعہ طے شدہ یونیورسل پروٹوکول ہے۔ کرپٹو کرنسی ایکسچینج تک محدود نہیں، کوئی بھیRESTمعاہدہ یاFIXمعاہدے کے پلیٹ فارم تک بھی رسائی حاصل کی جا سکتی ہے۔
یہ مضمون کرے گاRESTپروٹوکول تک رسائی کو ایک مثال کے طور پر لیتے ہوئے، یہ بتاتا ہے کہ OKX ایکسچینج کے API کو سمیٹنے اور اس تک رسائی حاصل کرنے کے لیے Inventor Quantitative Trading Platform کے عمومی پروٹوکول کو کیسے استعمال کیا جائے۔ جب تک کہ دوسری صورت میں وضاحت نہ کی گئی ہو، یہ مضمون REST جنرل پروٹوکول کا حوالہ دیتا ہے۔
موجد مقداری تجارتی پلیٹ فارم پر تبادلے کو ترتیب دینے کا صفحہ:
托管者上运行的策略实例 -> 通用协议程序
عمل کے دوران، میزبان جانتا ہے کہ عام پروٹوکول پروگرام تک کہاں رسائی حاصل کرنی ہے۔
مثال کے طور پر:http://127.0.0.1:6666/OKX
عام طور پر، مشترکہ پروٹوکول پروگرام اور میزبان ایک ہی ڈیوائس (سرور) پر چلائے جاتے ہیں، اس لیے سروس ایڈریس کو لوکل مشین (لوکل ہوسٹ) کے طور پر لکھا جاتا ہے، اور پورٹ ایک ایسی بندرگاہ ہو سکتی ہے جس پر سسٹم کا قبضہ نہ ہو۔托管者上运行的策略实例 -> 通用协议程序
تبادلے کی ترتیب کی معلومات اس عمل کے دوران گزر گئی۔托管者上运行的策略实例 -> 通用协议程序
تبادلے کی ترتیب کی معلومات اس عمل کے دوران گزر گئی۔مضمون میں ظاہر کردہ OKX پلگ ان کنفیگریشن کا اسکرین شاٹ درج ذیل ہے:
OKX تبادلہ خفیہ کلیدی ترتیب کی معلومات:
accessKey: accesskey123 // accesskey123 这些并不是实际秘钥,仅仅是演示
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 سپاٹ ایکسچینج، Python جنرل پروٹوکول کی مثال میں CustomProtocolOKX کلاس کا نفاذ دیکھیںGetTicker
、GetAccount
اور دیگر افعال۔
جب عام پروٹوکول پروگرام ایکسچینج کے API انٹرفیس تک رسائی حاصل کرتا ہے، کچھ آپریشن کرتا ہے یا کچھ ڈیٹا حاصل کرتا ہے، تو اسے نتائج کو محافظ کو واپس کرنے کی ضرورت ہوتی ہے۔
نگہبان کو دیا گیا ڈیٹا حکمت عملی کے ذریعے بلائے گئے انٹرفیس کے مطابق مختلف ہوتا ہے، اور اسے پہلے دو قسموں میں تقسیم کیا جاتا ہے:
{
"data": null, // "data" can be of any type
"raw": null // "raw" can be of any type
}
ڈیٹا: اس فیلڈ کا مخصوص ڈھانچہ وہی ہے جو عام پروٹوکول پروگرام کے ذریعے موصول ہونے والی درخواست میں ہے۔method
ذیل میں FMZ پلیٹ فارم API فنکشن کے ذریعے واپس کیے گئے ڈیٹا ڈھانچے کی تعمیر کے لیے استعمال ہونے والے تمام انٹرفیسز کی فہرست ہے۔
Raw: اس فیلڈ کو ایکسچینج API جواب کے خام ڈیٹا میں منتقل کرنے کے لیے استعمال کیا جا سکتا ہے، مثال کے طور پرexchange.GetTicker()
فنکشن کے ذریعے لوٹائے گئے ٹکر ڈھانچے میں درج ذیل معلومات ٹکر ڈھانچے کے انفارمیشن فیلڈ میں درج ہوتی ہیں۔raw
فیلڈز اورdata
فیلڈ کا ڈیٹا؛ کچھ پلیٹ فارم API فنکشنز کو اس ڈیٹا کی ضرورت نہیں ہے۔
جنرل پروٹوکول پروگرام ایکسچینج انٹرفیس کو کال کرنے میں ناکام رہا (کاروباری غلطی، نیٹ ورک کی خرابی، وغیرہ)
{
"error": "" // "error" contains an error message as a string
}
پالیسی پروگرام کو موصول ہونے والے عمومی پروٹوکول جوابی ڈیٹا کو ظاہر کرتا ہے:
// FMZ平台的调试工具中测试
function main() {
Log(exchange.GetTicker("USDT")) // 交易对不完整,缺少BaseCurrency部分,需要通用协议插件程序返回报错信息: {"error": "..."}
Log(exchange.GetTicker("LTC_USDT"))
}
مندرجہ بالا ایک مختصر عمل ہے کہ کس طرح عام پروٹوکول پروگرام (FMZ unpackaged) ایکسچینج API تک رسائی حاصل کرتا ہے یہ عمل صرف اس بات کی وضاحت کرتا ہے کہ (FMZ) پلیٹ فارم ڈیبگنگ ٹول کو کیسے کال کرنا ہے۔exchange.GetTicker()
فنکشن کا عمل۔ اس کے بعد، تمام پلیٹ فارم API فنکشنز کے تعامل کی تفصیلات کو تفصیل سے بیان کیا جائے گا۔
پلیٹ فارم مختلف ایکسچینجز کے مشترکہ افعال کو سمیٹتا ہے اور انہیں ایک مخصوص فنکشن میں یکجا کرتا ہے، جیسے کہ گیٹ ٹِکر فنکشن، جو کسی خاص پروڈکٹ کی موجودہ مارکیٹ کی معلومات کی درخواست کرتا ہے، یہ بنیادی طور پر ایک API ہے جو تمام ایکسچینجز کے پاس ہے۔ لہذا جب حکمت عملی کی مثال میں پلیٹ فارم encapsulated API انٹرفیس تک رسائی حاصل کی جاتی ہے، تو نگران “یونیورسل پروٹوکول” پلگ ان (اوپر مذکور) کو ایک درخواست بھیجے گا۔
POST /OKX HTTP/1.1
{
"access_key": "xxx",
"method": "ticker",
"nonce": 1730275031047002000,
"params": {"symbol":"LTC_USDT"},
"secret_key": "xxx"
}
حکمت عملی میں مختلف موجد پلیٹ فارم encapsulated API فنکشنز کو کال کرتے وقت (جیسے GetTicker)، نگران کی طرف سے جنرل پروٹوکول کو بھیجی جانے والی درخواست کی شکل بھی مختلف ہوگی۔ جسم میں ڈیٹا (JSON) صرف اس میں مختلف ہے۔method
اورparams
. عام پروٹوکول کو ڈیزائن کرتے وقت،method
آپ مواد کی بنیاد پر مخصوص آپریشن کر سکتے ہیں۔ مندرجہ ذیل تمام انٹرفیس کے لیے درخواست کے جواب کے منظرنامے ہیں۔
مثال کے طور پر، موجودہ تجارتی جوڑی ہے:ETH_USDT
، میں بعد میں تفصیلات میں نہیں جاؤں گا۔ وہ ڈیٹا جس کا نگران عام پروٹوکول سے جواب دینے کی توقع کرتا ہے وہ بنیادی طور پر ڈیٹا فیلڈ میں لکھا جاتا ہے، اور ایکسچینج انٹرفیس کے اصل ڈیٹا کو ریکارڈ کرنے کے لیے ایک خام فیلڈ بھی شامل کیا جا سکتا ہے۔
GetTicker
{"symbol":"ETH_USDT"}
{
"data": {
"symbol": "ETH_USDT", // 对应GetTicker函数返回的Ticker结构中的Symbol字段
"buy": "2922.18", // ...对应Buy字段
"sell": "2922.19",
"high": "2955",
"low": "2775.15",
"open": "2787.72",
"last": "2922.18",
"vol": "249400.888156",
"time": "1731028903911"
},
"raw": {} // 可以增加一个raw字段记录交易所API接口应答的原始数据
}
GetDepth
{"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]
// ...
]
}
}
GetTrades
{"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",
}
// ...
]
}
GetRecords
{
"limit":"500",
"period":"60", // 60分钟
"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],
// ...
]
}
GetMarkets عمل میں لایا جائے۔
{}
{}
GetTickers عمل میں لایا جائے۔
{}
{}
GetAccount
{}
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
GetAssets
{}
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
CreateOrder / Buy / Sell
{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
{
"data": {
"id": "BTC-USDT,123456"
}
}
GetOrders
{"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"
},
// ...
]
}
GetOrder
{
"id":"ETH-USDT,123456", // 策略中调用: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, // 如果交易所没有提供,在处理时可以赋值为0
}
}
GetHistoryOrders
{"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"
},
// ...
]
}
CancelOrder
{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
{
"data": true // 只要该JSON中没有error字段,都默认为撤单成功
}
IO
Exchange.IO فنکشن کو براہ راست ایکسچینج انٹرفیس تک رسائی حاصل کرنے کے لیے استعمال کیا جاتا ہے، مثال کے طور پر، ہم استعمال کرتے ہیں۔
GET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDT
مثال کے طور پر۔
// 策略实例中调用
exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
طریقہ کا میدان:"__api_/api/v5/trade/orders-pending"
میتھڈ فیلڈ _api سے شروع ہوتی ہے، جو اس بات کی نشاندہی کرتی ہے کہ یہ حکمت عملی مثال میں exchange.IO فنکشن کال کے ذریعے متحرک ہے۔
پیرامز فیلڈ:
{"instId":"ETH-USDT","instType":"SPOT"} // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
وہ ڈیٹا جس کی میزبان کو عام پروٹوکول کے جواب میں توقع ہے:
{
"data": {"code": "0", "data": [], "msg": ""} // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
}
دوسرے
حکمت عملی کی مثالوں میں استعمال ہونے والے دیگر موجد پلیٹ فارم API افعال، جیسے:
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'
if httpMethod == "GET":
jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else ""
else:
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()