上一章我们了解了期限套利的原理,寻找到了最大资金费率的数字货币,对接了OKEX交易所的API,这一节,我们将完善上一章节代码,自动化交易,添加钉钉机器人来进行消息提醒。
MidClass,py
class MidClass(object):
# 初始化
def __init__(self, ThisExchange, symbol):
self.Exchange = ThisExchange # 传入交易所实例对象
self.Symbol = symbol # 传入币种
market = ThisExchange.markets[self.Symbol]
self.MinLeverage = market['limits']['leverage']['min'] # 最小杠杆倍数
self.MaxLeverage = market['limits']['leverage']['max'] # 最大杠杆倍数
self.AmountPrecision = len(str(market['precision']['amount']).split(".")[-1]) # 下单数量精度,小数点后几位小数
self.PricePrecision = len(str(market['precision']['price']).split(".")[-1]) # 价格精度,小数点后几位小数
self.TakerFee = ThisExchange.markets[symbol]["taker"] # taker手续费
self.MakerFee = ThisExchange.markets[symbol]["maker"] # maker手续费
self.LimitAmount = market['limits']['amount']['min'] # 最小下单数限制
# 获得交易对行情信息
def GetTicker(self):
self.High = '___'
self.Low = '___'
self.Buy = '___'
self.Sell = '___'
self.Last = '___'
try:
self.Ticker = self.Exchange.fetchTicker(self.Symbol)
self.datetime = self.Ticker["datetime"]
self.High = self.Ticker['high']
self.Low = self.Ticker['low']
self.Buy = self.Ticker['bid']
self.Sell = self.Ticker['ask']
self.Last = self.Ticker['last']
return True # 只要有一个成功就返回True
except:
return False # 如果全都获取不了返回False
# 判断传入的币是现货还是合约针对okex
@staticmethod
def IsSpot(Symbol):
if len(Symbol.split(':')) == 1:
return True
else:
return False
# 获得账户对于该交易对信息 只显示交易过的币种
def GetAccount(self):
self.Account = '___'
self.Balance = '___'
self.FrozenBalance = '___'
self.Stocks = '___'
self.FrozenStocks = '___'
if MidClass.IsSpot(self.Symbol):
self.SymbolStocksName = self.Symbol.split('/')[0]
self.SymbolBalanceName = self.Symbol.split('/')[1]
else:
self.SymbolStocksName = self.Symbol.split(":")[0].split("/")[0]
self.SymbolBalanceName = self.Symbol.split(":")[-1]
try:
self.Account = self.Exchange.fetchBalance()
self.Balance = self.Account[self.SymbolBalanceName]['free']
self.FrozenBalance = self.Account[self.SymbolBalanceName]['used']
self.Stocks = self.Account[self.SymbolStocksName]['free']
self.FrozenStocks = self.Account[self.SymbolStocksName]['used']
return True
except RuntimeError:
raise ("账户没有交易过这个币种!")
return False
# 确认是否获取到账户和交易对信息
def RefreshData(self):
if not self.GetAccount():
return 'false get account'
if not self.GetTicker():
return 'false get ticker'
return 'refresh data finish!'
# 创建买入订单
def CreatebuyOrder(self, OrderType, Price, Amount, order_type="limit"):
"""
Args:
OrderType: buy \ sell \ short \ long
Price:
Amount: 现货——币的数量 合约——张数
order_type: limit \ market
Returns: 订单id
"""
if round(Amount, self.AmountPrecision) < self.LimitAmount:
raise RuntimeError("下单数量小于最小下单量,请加大保证金,或者提高杠杆!")
if OrderType == 'buy':
# 执行买单杠杆交易
params = {
"tdMode": "cross",
"ccy": "USDT"
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="buy",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
elif OrderType == 'sell':
# 执行卖单杠杆交易
params = {
"tdMode": "cross",
"ccy": "USDT"
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="sell",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
elif OrderType == 'short':
# 执行开空合约
params = {
"tdMode": "cross",
"posSide": "short"
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="sell",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
elif OrderType == 'long':
# 执行开多合约
params = {
"tdMode": "cross",
"posSide": "long"
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="buy",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
else:
pass
self.GetAccount()
return OrderId
# 创建卖出订单
def CreateSellOrder(self, OrderType, Price, Amount,order_type="limit"):
if OrderType == 'buy':
# 平空
params = {
"tdMode": "cross",
}
OrderId = self.Exchange.create_order(self.Symbol, type==order_type, side="buy",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
elif OrderType == 'sell':
# 平多
params = {
"tdMode": "cross",
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="sell",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
elif OrderType == 'short':
# 执行平空合约
params = {
"tdMode": "cross",
"posSide": "short"
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="buy",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
elif OrderType == 'long':
# 执行平多合约
params = {
"tdMode": "cross",
"posSide": "long"
}
OrderId = self.Exchange.create_order(self.Symbol, type=order_type, side="sell",
amount=round(Amount, self.AmountPrecision),
price=round(Price, self.PricePrecision), params=params)["id"]
else:
pass
self.GetAccount()
return OrderId
# 获取订单状态
def GetOrder(self, Idd):
self.OrderId = '___'
self.OrderPrice = '___'
self.OrderNum = '___'
self.OrderDealNum = '___'
self.OrderAvgPrice = '___'
self.OrderStatus = '___'
try:
self.Order = self.Exchange.fetchOrder(Idd, self.Symbol)
self.OrderId = self.Order['id']
self.OrderPrice = self.Order['price']
self.OrderNum = self.Order['amount']
self.OrderDealNum = self.Order['filled']
self.OrderAvgPrice = self.Order['average']
self.OrderStatus = self.Order['status']
return True
except:
return False
# 取消订单
def CancelOrder(self, Idd):
self.CancelResult = '___'
try:
self.CancelResult = self.Exchange.cancelOrder(Idd, self.Symbol)
return True
except:
return False
# 获取k线数据
def GetRecords(self, Timeframe='1m'):
self.Records = '___'
try:
self.Records = self.Exchange.fetchOHLCV(self.Symbol, Timeframe)
return True
except:
return False
# 设置杠杆mgnMode:isolated/cross
def SetLeverage(self, leverage, params=None):
if MidClass.IsSpot(self.Symbol):
if params is None:
params = {"mgnMode": "cross"}
else:
if params is None:
params = {"mgnMode": "cross"}
try:
if leverage <= self.MaxLeverage and leverage >= self.MinLeverage:
self.LeverageInfo = self.Exchange.set_leverage(leverage, self.Symbol, params=params)
return True
except RuntimeError as e:
print(e, "请注意杠杆倍数,不能超过交易所设定倍数!")
return False
message.py
import json
import requests
# 类对象二: 钉钉机器人API的调度
class Message(object):
def __init__(self, MyMid):
self.MyMid = MyMid
# 钉钉机器人消息API
def msg(self, text):
headers = {'Content-Type': 'application/json;charset=utf-8'}
# 只需更改这一项,将双引号内容替换为刚才复制的Webhook地址
token = "token"
api_url = f"https://oapi.dingtalk.com/robot/send?access_token={token}"
json_text = {
"msgtype": "text",
"text": {
"content": "msg:" + text
}
}
requests.post(api_url, json.dumps(json_text), headers=headers)
def ShowInfo(self):
self.msg(f"交易所时间:{self.MyMid.datetime}\n{self.MyMid.Symbol}的最新价: {self.MyMid.Last}\n"
+ '该币种可用额度为:%s,%s\n' % (round(self.MyMid.Stocks, 2), self.MyMid.SymbolStocksName) +
'该币种冻结额度为:%s,%s\n' % (round(self.MyMid.FrozenStocks, 2), self.MyMid.SymbolStocksName)
+ '账户可用额度为:%s\n' % round(self.MyMid.Balance, 2) + 'USDT' +
'账户冻结额度为:%s\n' % round(self.MyMid.FrozenBalance, 2) + 'USDT' +
'该币种taker手续费%s\n' % self.MyMid.TakerFee +
'该币种Maker手续费%s\n' % self.MyMid.MakerFee
)
def ShowInfoOrder(self):
self.msg(f"订单ID:%s" % self.MyMid.OrderId)
self.msg(f"订单Price:%s" % self.MyMid.OrderPrice)
self.msg(f"订单数量:%s" % self.MyMid.OrderNum)
self.msg(f"订单成交了:%s" % self.MyMid.OrderDealNum)
self.msg(f"订单状态:%s" % self.MyMid.OrderStatus)
self.msg('-' * 40)
def show(self):
self.ShowInfo()
# self.ShowInfoOrder()
Arbitrage.py
from logger import logger
class Arbitrage():
# 传入实例化的类
def __init__(self, MyMid1, MyMid2, FundingRate, leverage=None):
self.MyMid1 = MyMid1
self.MyMid2 = MyMid2
self.leverage = leverage
self.MyMid1_Fee = 2 * MyMid1.TakerFee
self.MyMid2_Fee = 2 * MyMid2.TakerFee
self.FundingRate = FundingRate
self.SentOrders = [] # 创建一个订单列表,可以记录目前的委托订单状态
# 计算是否可以获利,如果资金费率大于手续费的话,返回True
def CalculateProfile(self):
diff = abs(self.FundingRate) - self.MyMid1_Fee - self.MyMid2_Fee
print(diff)
if diff > 0:
logger.info("此次套利的价差为:%s" % diff)
return True
else:
return False
# 期现套利运行中
def run(self):
# 如果资金费率大于0,买入现货,卖出合约
if self.FundingRate > 0 and self.CalculateProfile() == True:
if self.leverage:
self.MyMid1.SetLeverage(self.leverage)
self.MyMid2.SetLeverage(self.leverage)
# 设置资金
self.totalMoney = self.MyMid1.Balance * self.leverage
self.trading(1)
# 没有杠杆的话:
else:
self.totalMoney = self.MyMid1.Balance
self.trading(1)
# 如果资金费率小于0,卖出现货,买入合约
elif self.FundingRate < 0 and self.CalculateProfile() == True:
# 如果带有杠杆,设置杠杆
if self.leverage:
self.MyMid1.SetLeverage(self.leverage)
self.MyMid2.SetLeverage(self.leverage)
# 设置资金
self.totalMoney = self.MyMid1.Balance * self.leverage
self.trading(-1)
# 没有杠杆的话:
else:
self.totalMoney = self.MyMid1.Balance
self.trading(-1)
def trading(self, flag):
if flag < 0:
# 开多合约
price1 = (self.MyMid1.Buy + self.MyMid1.Sell) / 2
Amount1 = self.totalMoney * 0.4 / (price1 * self.MyMid1.Price_Precision)
print("精度%s"%self.MyMid1.Price_Precision)
print("合约%s"% Amount1)
info1 = self.MyMid1.CreatebuyOrder("long", price1, Amount1)
self.SentOrders.append(info1)
logger.info(info1)
# 卖空现货
price2 = (self.MyMid2.Buy + self.MyMid2.Sell) / 2
Amount2 = Amount1 * self.MyMid1.Price_Precision
print("现货%s"% Amount2)
info2 = self.MyMid2.CreatebuyOrder("sell", price2, Amount2)
self.SentOrders.append(info2)
logger.info(info2)
if flag > 0:
# 开空合约
price1 = (self.MyMid1.Buy + self.MyMid1.Sell) / 2
Amount1 = self.totalMoney * 0.4 / (price1 * self.MyMid1.Price_Precision)
print("合约%s"% Amount1)
info1 = self.MyMid1.CreatebuyOrder("short", price1, Amount1)
self.SentOrders.append(info1)
logger.info(info1)
# # 买多现货
price2 = (self.MyMid2.Buy + self.MyMid2.Sell) / 2
Amount2 = Amount1 * self.MyMid1.Price_Precision
print("现货%s"% Amount2)
info2 = self.MyMid2.CreatebuyOrder("buy", price2, Amount2)
self.SentOrders.append(info2)
logger.info(info2)
FundingRate,py
import json
import pandas as pd
import os
from tqdm import tqdm
import requests
# 代理proxy
os.environ["http_proxy"] = "http://127.0.0.1:12307"
os.environ["https_proxy"] = "http://127.0.0.1:12307"
class FundingRate():
def __init__(self, exchange):
self.exchange = exchange
def fetchSwapMarket(self):
url = f"https://www.okex.com/api/v5/market/tickers?instType=SWAP"
res = requests.get(url)
json_res = json.loads(res.content)
list_ = []
if json_res["code"] == "0":
for i in json_res["data"]:
list_.append(i["instId"])
return list_
def findMaxfee(self):
coin_rate = {}
swap_coin = self.fetchSwapMarket()
print("正在寻找目标币种,请等待!")
# 总进度
total = len(swap_coin)
with tqdm(total=total) as pbar:
pbar.set_description('Processing:')
for i, coin in enumerate(swap_coin):
coin_rate[coin] = self.exchange.fetchFundingRate(coin)
pbar.update(1)
df = pd.DataFrame(coin_rate).T
df1 = df[["fundingRate", "nextFundingRate", "fundingTimestamp", "fundingDatetime"]]
df1 = df1.sort_values(by="nextFundingRate")
fundingTimestamp = df1["fundingTimestamp"][0]
# 保存文件
root = os.path.abspath(os.curdir)
path = os.path.join(root, f"datas\{fundingTimestamp}.csv")
df1.to_csv(path, index=True)
return df1
# -*- coding: utf-8 -*-
import sys
import logging
from logging import handlers
# 日志级别关系映射
level_relations = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'crit': logging.CRITICAL
}
def _get_logger(filename, level='info'):
# 创建日志对象
log = logging.getLogger(filename)
# 设置日志级别
log.setLevel(level_relations.get(level))
# 日志输出格式
fmt = logging.Formatter('%(asctime)s %(thread)d %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
# 输出到控制台
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(fmt)
# 输出到文件
# 日志文件按天进行保存,每天一个日志文件
file_handler = handlers.TimedRotatingFileHandler(filename=filename, when='D', backupCount=1, encoding='utf-8')
# 按照大小自动分割日志文件,一旦达到指定的大小重新生成文件
# file_handler = handlers.RotatingFileHandler(filename=filename, maxBytes=1*1024*1024*1024, backupCount=1, encoding='utf-8')
file_handler.setFormatter(fmt)
log.addHandler(console_handler)
log.addHandler(file_handler)
return log
# 明确指定日志输出的文件路径和日志级别
logger = _get_logger(r'D:\backtrader\MMQT\logs\mylog.log', 'info')
import os
import ccxt
import pandas as pd
from MMQT.Arbitrage import Arbitrage
from MMQT.FundingRate import FundingRate
from MMQT.Message import Message
from MMQT.MidClass import MidClass
from logger import logger
# 代理proxy
os.environ["http_proxy"] = "http://127.0.0.1:21882"
os.environ["https_proxy"] = "http://127.0.0.1:21882"
# 连接交易所
exchange_id = "okx"
exchange_class = getattr(ccxt, exchange_id)
exchange = exchange_class({
'apiKey': 'apiKey',
'secret': 'secret',
'password': 'password',
'enableRateLimit': True,
})
if __name__ == '__main__':
exchange.load_markets()
# 选币策略实例化
fundingRate = FundingRate(exchange)
# df = fundingRate.findMaxfee() # 输出到datas\
df = pd.read_csv(r"D:\backtrader\MMQT\datas\1653696000000.csv")
tmp = df.values[-1][0]
symbol = tmp.split('-')[0] + "/USDT:USDT"
symbol2 = symbol.split("/")[0] + "/USDT"
print(symbol)
print(symbol2)
rate = df["nextFundingRate"][0]
# 中间模块实例化
MyMid1 = MidClass(exchange, symbol=symbol)
MyMid2 = MidClass(exchange, symbol=symbol2)
# # 数据更新
MyMid1.RefreshData()
MyMid2.RefreshData()
# 显示相关数据,输出到钉钉
msg1 = Message(MyMid1)
msg2 = Message(MyMid2)
msg1.show()
msg2.show()
# 策略运行
# # arbitrage = Arbitrage(MyMid1,MyMid2,rate,leverage=5)
# # arbitrage.run()