【币圈小试牛刀】期现套利(下)——完善套利代码

前言

上一章我们了解了期限套利的原理,寻找到了最大资金费率的数字货币,对接了OKEX交易所的API,这一节,我们将完善上一章节代码,自动化交易,添加钉钉机器人来进行消息提醒。

项目目录结构

【币圈小试牛刀】期现套利(下)——完善套利代码_第1张图片

中间件

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()

你可能感兴趣的:(策略,金融,python,数字货币,区块链,开发语言)