[TOC]
ইনভেন্টর কোয়ালিফাইড ট্রেডিং প্ল্যাটফর্মগুলি অনেকগুলি ক্রিপ্টোকারেন্সি এক্সচেঞ্জকে সমর্থন করে এবং বাজারে মূলধারার এক্সচেঞ্জগুলিকে আবৃত করে। তবে, এখনও অনেকগুলি এক্সচেঞ্জ এখনও আবৃত নয় এবং ব্যবহারকারীরা যারা এই এক্সচেঞ্জগুলি ব্যবহার করতে চান তাদের জন্য ইনভেন্টর কোয়ালিফাইড সাধারণ প্রোটোকলের মাধ্যমে অ্যাক্সেস করা যায়। কেবলমাত্র ক্রিপ্টোকারেন্সি এক্সচেঞ্জগুলিতে সীমাবদ্ধ নয়, যে কোনও সমর্থনবিশ্রামচুক্তি বাফিক্সএই চুক্তির প্ল্যাটফর্মগুলিও অ্যাক্সেসযোগ্য।
এই নিবন্ধটিবিশ্রামপ্রোটোকল অ্যাক্সেস উদাহরণস্বরূপ, কিভাবে ইনভেন্টরদের কোয়ালিফাইড ট্রেডিং প্ল্যাটফর্মের সাধারণ প্রোটোকল ব্যবহার করে OKX এক্সচেঞ্জের API গুলিকে আবরণ এবং অ্যাক্সেস করা যায় তা ব্যাখ্যা করে।
ইনভেন্টর কোয়ালিফাইড ট্রেডিং প্ল্যাটফর্মের এক্সচেঞ্জ কনফিগারেশন পৃষ্ঠাঃ
托管者上运行的策略实例 -> 通用协议程序
এই প্রক্রিয়ার সময়, ট্রাস্টি জানে কোথায় জেনারেল প্রোটোকল প্রসেস অ্যাক্সেস করতে হবে।
উদাহরণস্বরূপঃhttp://127.0.0.1:6666/OKX
এটি সাধারণত একটি সার্ভারে একটি হোস্টের সাথে একই ডিভাইসে একটি সাধারণ প্রোটোকল প্রোগ্রাম চালায়, তাই পরিষেবাটির ঠিকানাটি স্থানীয় হোস্টে লেখা হয় এবং পোর্টটি একটি সিস্টেম অব্যবহৃত ব্যবহার করে।托管者上运行的策略实例 -> 通用协议程序
এই প্রক্রিয়ার সময়, বিনিময় কনফিগারেশন তথ্য প্রেরণ করা হয়।托管者上运行的策略实例 -> 通用协议程序
এই প্রক্রিয়ার সময়, বিনিময় কনফিগারেশন তথ্য প্রেরণ করা হয়।এই নিবন্ধে প্রকাশিত OKX প্লাগইন কনফিগারেশনের স্ক্রিনশট নিচে দেওয়া হলঃ
ওকেএক্স এক্সচেঞ্জের গোপন কী কনফিগারেশনঃ
accessKey: accesskey123 // accesskey123 这些并不是实际秘钥,仅仅是演示
secretKey: secretkey123
passphrase: passphrase123
১। প্রশাসক ইনভেন্টর কোয়ালিফাইড ট্রেডিং প্ল্যাটফর্মে যে কোনও কৌশল বাস্তবায়ন করার জন্য একটি ট্রাস্ট স্থাপন করা আবশ্যক।
২। সাধারণ প্রোটোকল প্রোগ্রাম (প্লাগইন)
হোস্ট এবং সাধারণ প্রোটোকলগুলি সাধারণত একই ডিভাইসে স্থাপন করা হয়, এবং সাধারণ প্রোটোকল (পরিষেবা) প্রোগ্রামগুলি ডিজাইন লিখতে যে কোনও ভাষা ব্যবহার করতে পারে, এটি পাইথন 3 ব্যবহার করে লিখিত হয়। এটি সরাসরি কার্যকর হয়, যেমনটি কোনও পাইথন প্রোগ্রাম চালানো হয় (প্রাক-প্রস্তুত পাইথন পরিবেশের বিভিন্ন কনফিগারেশন) ।
অবশ্যই, এফএমজেডে পাইথন প্রোগ্রাম চালানোর জন্য সমর্থন রয়েছে, তবে এটি একটি সাধারণ প্রোটোকলকে একটি বাস্তব ডিস্ক হিসাবে চালিত করতে পারে, যা উদ্ভাবকদের একটি পরিমাণগত ট্রেডিং প্ল্যাটফর্মের জন্য আনপ্যাকেটেড এক্সচেঞ্জ এপিআই অ্যাক্সেসের জন্য সমর্থন সরবরাহ করে।
সাধারণ প্রোটোকল প্রক্রিয়ার পরে, শোনা শুরু করুনঃ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"}
。যখন ইউপিএল প্রসেসরটি ট্রাস্টি থেকে একটি অনুরোধ পায়, তখন অনুরোধে থাকা তথ্যের ভিত্তিতে জানতে পারেঃ নীতি অনুরোধ করা প্ল্যাটফর্ম এপিআই ফাংশন ((প্যারামিটার তথ্য সহ) ), এক্সচেঞ্জের গোপন কী ইত্যাদি।
এই তথ্যের উপর ভিত্তি করে, একটি সাধারণ প্রোটোকল প্রোগ্রাম এক্সচেঞ্জ ইন্টারফেসে প্রবেশ করতে পারে, প্রয়োজনীয় ডেটা পেতে পারে বা কিছু ক্রিয়াকলাপ সম্পাদন করতে পারে।
সাধারণত এক্সচেঞ্জ ইন্টারফেসে জিইটি/পিওএসটি/পিইটি/ডিলেট ইত্যাদি পদ্ধতি থাকে।
জেনারেল প্রোটোকল প্রক্রিয়ার মাধ্যমে এক্সচেঞ্জ ইন্টারফেসের প্রতিক্রিয়া ডেটা পাওয়া যায়, যা পরবর্তী প্রক্রিয়াকরণের জন্য পরিচালকের প্রত্যাশিত ডেটা গঠন করে (নিচে বর্ণনা করা হয়েছে) ।
OKX এক্সচেঞ্জের জন্য রেফারেন্স, Python সাধারণ প্রোটোকল দৃষ্টান্তের মধ্যে CustomProtocolOKX শ্রেণীর বাস্তবায়নGetTicker
、GetAccount
সমান ফাংশন।
যখন একটি সাধারণ প্রোটোকল প্রোগ্রাম একটি এক্সচেঞ্জের এপিআই ইন্টারফেসে প্রবেশ করে, কিছু ক্রিয়াকলাপ সম্পাদন করে বা কিছু ডেটা অর্জন করে, তখন এটির ফলাফলগুলি ট্রাস্টিকে ফিডব্যাক করতে হয়।
নীতিগত কলের ইন্টারফেসের উপর ভিত্তি করে হোস্টের কাছে প্রতিক্রিয়া ডেটা আলাদা হয়, প্রথমে দুটি প্রধান বিভাগে বিভক্ত করা হয়ঃ
ইউপিওর মাধ্যমে এক্সচেঞ্জ ইন্টারফেসের কল সফল হয়েছেঃ
{
"data": null, // "data" can be of any type
"raw": null // "raw" can be of any type
}
method
এফএমজেড প্ল্যাটফর্মের এপিআই ফাংশনটি শেষ পর্যন্ত ফিরে আসার জন্য ব্যবহৃত ডেটা কাঠামোর সাথে সম্পর্কিত, নীচে সমস্ত ইন্টারফেসের তালিকা রয়েছে।exchange.GetTicker()
ফাংশনটির টিকার স্ট্রাকচার, টিকার স্ট্রাকচারের ইনফো ফীল্ডে যা রেকর্ড করা হয় তা হলraw
ক্ষেত্র এবংdata
কিছু ক্ষেত্রের ডেটা; প্ল্যাটফর্মের এপিআই ফাংশনগুলির জন্য, কিছু এই ডেটা প্রয়োজন হয় না।এক্সচেঞ্জ ইন্টারফেসের জন্য সাধারণ প্রোটোকল প্রোগ্রাম কল ব্যর্থ হয়েছে (ব্যবসায়িক ত্রুটি, নেটওয়ার্ক ত্রুটি ইত্যাদি)
{
"error": "" // "error" contains an error message as a string
}
ডেমোগ্রাফিক স্ট্র্যাটেজি প্রোগ্রামের দ্বারা প্রাপ্ত জেনেরাল প্রোটোকল প্রতিক্রিয়া ডেটাঃ
// FMZ平台的调试工具中测试
function main() {
Log(exchange.GetTicker("USDT")) // 交易对不完整,缺少BaseCurrency部分,需要通用协议插件程序返回报错信息: {"error": "..."}
Log(exchange.GetTicker("LTC_USDT"))
}
উপরের বিষয়বস্তুটি সাধারণ প্রোটোকল প্রোগ্রামগুলিকে অ্যাক্সেস করার জন্য একটি সংক্ষিপ্ত প্রক্রিয়া (FMZ Unwrapped) এক্সচেঞ্জ এপিআই, যা কেবলমাত্র FMZ প্ল্যাটফর্ম ডিবাগিং সরঞ্জামগুলিতে কলগুলি ব্যাখ্যা করে।exchange.GetTicker()
ফাংশন সময় প্রক্রিয়া. সমস্ত প্ল্যাটফর্ম এপিআই ফাংশনগুলির মধ্যে পারস্পরিক সম্পর্কিত বিবরণ নীচে বিস্তারিতভাবে বর্ণনা করা হয়েছে।
প্ল্যাটফর্মটি বিভিন্ন এক্সচেঞ্জের সাধারণ বৈশিষ্ট্যগুলিকে একত্রিত করে, যেমন GetTicker ফাংশন, যা বর্তমানের বিভিন্ন ধরণের বাজারের তথ্যের জন্য অনুরোধ করে, যা মূলত সমস্ত এক্সচেঞ্জের জন্য উপলব্ধ একটি এপিআই। সুতরাং যখন নীতির উদাহরণে প্ল্যাটফর্মের প্যাকেজযুক্ত এপিআই ইন্টারফেসটি অ্যাক্সেস করা হয়, তখন প্রশাসক "সাধারণ প্রোটোকল" প্লাগইন প্রোগ্রামকে অনুরোধ পাঠায় ((উপরে উল্লেখ করা হয়েছে):
POST /OKX HTTP/1.1
{
"access_key": "xxx",
"method": "ticker",
"nonce": 1730275031047002000,
"params": {"symbol":"LTC_USDT"},
"secret_key": "xxx"
}
বিভিন্ন উদ্ভাবক প্ল্যাটফর্মের প্যাকেজযুক্ত API ফাংশন (যেমন GetTicker) কল করার সময়, হোস্টগুলি ইউপিএল-এ অনুরোধের বিন্যাসও আলাদা করে।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接口应答的原始数据
}
গভীরতা পান
{"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বাস্তবায়ন
{}
{}
অ্যাকাউন্ট পান
{}
{
"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"},
// ...
]
}
ক্রিয়েটঅর্ডার / কিনুন / বিক্রি করুন
{"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", // 策略中调用: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
}
}
GetHistoryঅর্ডার
{"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 // 只要该JSON中没有error字段,都默认为撤单成功
}
আইও
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 应答的数据
}
অন্যান্য
কৌশল উদাহরণে ব্যবহৃত অন্যান্য উদ্ভাবক প্ল্যাটফর্ম এপিআই ফাংশন, যেমনঃexchange.Go()
、exchange.GetRawJSON()
যেমন ফাংশনগুলিকে প্যাকেজ করার প্রয়োজন নেই, কল করার পদ্ধতি, কার্যকারিতা পরিবর্তন হয় না।
ভবিষ্যৎ এক্সচেঞ্জগুলি সমস্ত অবিলম্বে এক্সচেঞ্জের ফাংশনগুলিকে সমর্থন করার পাশাপাশি কিছু ভবিষ্যৎ এক্সচেঞ্জের নির্দিষ্ট এপিআই ফাংশনগুলিও সমর্থন করে।
বাস্তবায়ন
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()