likes
comments
collection
share

币安挂单python实现

作者站长头像
站长
· 阅读数 4

写在前面的话

这个小项目是笔者的一个面试题,作者之前在学习量化策略的时候,喜欢实现一些策略,很多并未部署到币安实盘,但是这个项目是部署到币安上的项目,确实给我带来了一些挑战,不过也从中学到了不少东西,先学了python异步编程,以及多线程.可能是因为作者的梯子设置有问题,所以没能真正运行起来,不过整体的逻辑完成了,因为网络的问题,没有进行调试,应该有很多业务上或者技术上的缺陷,但是我觉得随着以后的进步,这些肯定可以解决

自己对于量化交易很感兴趣,接到面试的时候,

我欣喜若狂,因为

我在想这会不会是一生中仅有的机会可以从事这个行业,

希望面试可以通过.

下面是我的项目解析

币安挂单python实现

这里是一些必要的库,和很多需要的全局变量

import asyncio
import logging
import json
import websocket
from threading import Thread
from binance.lib.utils import config_logging
from binance.error import ClientError
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient
from websocket_client import BinanceWebsocketClient
from binance.um_futures import UMFutures
config_logging(logging, logging.DEBUG)

api_key = "vwE11TOZU5IHxXuXu0nZ4eIrNHLl0Pr5kWw59yDIGm5pK7k6X0DvExohpyZI"
api_secret = "OYLN7mk6iC7ypnV5UgKJj2BlnmsCzFBA6txxxrCbyI5HF7Liri0DbKssN"#密钥已经经过修改
symbol = "ETHUSDT"
start_order_amount = 100.0
max_order_amount = 1000.0
total_order_amount = 50000.0
price_move_limit = 0.1
price_margin_percent = 0.01
cancel_range_percentage=0.001
stop_loss_rate=0.01
buy_orders = []
sell_orders = []

global um_futures_client
global current_price

这里是止盈部分的代码,这里我参考了币安的文档,主要的逻辑是先获取账户信息,然后对于每一个仓位的盈利进行计算,然后进行止盈

币安挂单python实现

async def stop_profit(current_price):
    account_info = um_futures_client.account(recvWindow=6000)
    positions = account_info['positions'][id]

    for position in positions:
        symbol = position['symbol']
        position_size = float(position['positionAmt'])
        unrealized_profit = float(position['unRealizedProfit'])
        #对于每一个订单,只要盈利1%,那就可以平仓
        if symbol == 'ETHUSDT' and position_size != 0:
            # 计算订单盈利
            profit_percent = (unrealized_profit /
                              (position_size * current_price)) * 100

            if profit_percent >= 1.0 :#止盈
                # 平仓
                if position_size > 0:
                    await um_futures_client.new_order(
                        symbol=symbol,
                        side='SELL',  # 平仓
                        type='MARKET',
                        quantity=abs(position_size)
                    )
                else:
                    await um_futures_client.new_order(
                        symbol=symbol,
                        side='SELL',  # 平仓
                        type='MARKET',
                        quantity=abs(position_size)
                    )

这里是止损部分的代码,止损是全仓止损,计算全仓的亏损,然后对于所有的订单进行平仓

async def stop_loss():
    #注意是全仓亏损,所以这里的计算方式和止盈有所差别
    account_info = um_futures_client.account(recvWindow=6000)#先获取账户信息
    if account_info["totalUnrealizedProfit"]/account_info["totalInitialMargin"]>stop_loss_rate:
        for position in positions:
            if position_size > 0:
                    await um_futures_client.new_order(
                        symbol=symbol,
                        side='SELL',  # 平仓
                        type='MARKET',
                        quantity=abs(position_size)
                    )
                else:
                    await um_futures_client.new_order(
                        symbol=symbol,
                        side='SELL',  # 平仓
                        type='MARKET',
                        quantity=abs(position_size)
                    )



这里是布置订单的接口,要从初始化的客户端调用新订单的布置,这里参考了GitHub上面的样例

币安挂单python实现

async def place_order(side, price, amount):
    try:
        response = um_futures_client.new_order(
            symbol=symbol,
            side=side,
            type="LIMIT",
            quantity=amount,
            timeInForce="GTC",
            price=price,
        )
        logging.info(response)
    except ClientError as error:
        logging.error("Found error. status: {}, error code: {}, error message: {}".format(
            error.status_code, error.error_code, error.error_message
        ))
  

这里是取消订单的接口,同样参考了github上面币安给的样例

async def cancel_orders(side, order_id):
    try:
        response = um_futures_client.cancel_order(
            symbol=symbol, orderId=order_id, recvWindow=2000)
        logging.info(response)
    except ClientError as error:
        logging.error("Found error. status: {}, error code: {}, error message: {}".format(
            error.status_code, error.error_code, error.error_message
        ))

这里是订单控制,用于实现上下一共一百个订单的实现,作者思考这个函数花了很多的时间,可能还是有错误,但是应该基本上完善了,主要的逻辑就是把价格等分,然后上下价格设置订单,注意要把订单放在一个列表里面方便管理

sync def conduct_orders():
    while True:
        try:
            scope = current_price * price_margin_percent
            buy_price = current_price - scope
            sell_price = current_price + scope

            # 更新50买单
            for i in range(1, 51):
                order_price = buy_price - (scope * (25 - i) / 25)
                order_amount = start_order_amount + \
                    i * (total_order_amount / 50)
                order_amount = min(order_amount, max_order_amount)
                buy_orders.append((order_price, order_amount))

            # 更新50卖单
            for i in range(1, 51):
                order_price = sell_price + (scope * (i - 25) / 25)
                order_amount = start_order_amount + \
                    (i - 25) * (total_order_amount / 50)
                order_amount = min(order_amount, max_order_amount)
                sell_orders.append((order_price, order_amount))

            # 挂单
            for order in buy_orders + sell_orders:
                # IO等待
                await place_order("BUY" if order in buy_orders else "SELL", order[0], order[1])

            await asyncio.sleep(1)

        except Exception as e:
            print(f"Error conduct_orders: {e}")

这里也是非常核心的一段代码,目的是在什么情况下,进行订单的撤销,也就是行情波动的时候进行订单的撤销,业务逻辑上稍微有点复杂

async def delete_recent_orders():
    while True:
        try:
            open_orders = await um_futures_client.get_open_orders("symbol",recvWindow=100)
            if len(open_orders) > 0:
                min_price = min(float(order["price"]) for order in open_orders)
                max_price = max(float(order["price"]) for order in open_orders)
                if max_price-min_price >= current_price*cancel_range_percentage:
                    # 撤销撤销千分之一范围内的订单
                    for order in open_orders:
                        if float(order["price"]) >= min_price and float(order["price"]) <= max_price:
                            # IO等待
                            await cancel_orders(order["side"], order["orderId"])#先撤单
                            await place_order(order["side"],order["price"],order['amount'])#再挂单
                            if max_price-min_price>=0.05*current_price:#我的理解是波动百分之五在万分之五的下面,如果大于百分之五,那么一定大于万分之5行情剧烈波动,假设为百分之五
                                #这时拉远最近的挂单距离
                                if(order["side"]=="buy"):#如果是买单那就假设向上拉百分之10
                                    await place_order(order["side"],order["price"]*1.1,order["amount"])
                                if(order["side"]=="sell"):#如果是卖单,那就假设向下拉百分之10
                                    await place_order(order["side"],order["price"]*0.9,order["amount"])


        except Exception as e:
            print(f"ERROR delete_recent_orders{e}")

        await asynio.sleep(1)

回调函数,但是没有用到,

这里是一些回调函数,用于套接字的初始化,

def on_open():
    data = {
        "method": "SUBSCRIBE",
        "params": [
            "ethusdt@aggTrade",
            "ethusdt@depth"
        ],
        "id": 2
    }
    um_futures_client.send(data)


def on_close():
    print('on close')


def message_handler(_, message):
    print(message)


def on_error(error):
    print(f"on error:{error}")

主函数的实现

作者在这一段纠结了好久到底该怎么样创建套接字websocket,试了很多方法,都无法链接url,可能是梯子的问题,也可能是自己对于websocket应用的不太熟练

def target1():

    print("线程1运行")
    
    # socket = 'wss://fstream.binance.com/ws'
    # base_url = socket + '/ethusdt@trade'
    # my_client = websocket.WebSocketApp(
    #     socket + '/ethusdt@trade', on_open=on_open, on_message=message_handler, on_close=on_close)

    my_client = UMFuturesWebsocketClient(
        on_open=on_open,
        on_close=on_close,
        on_message=message_handler,
        on_error=on_error
    )  # 这里的url访问不了,我把梯子调成全局也不行
    # 创建异步时间并运行
    # my_client = BinanceWebsocketClient(
    #     base_url, on_open=on_open, on_close=on_close, on_error=on_error)
    task = [conduct_orders(), delete_recent_orders()]
    # 再写一个线程,进行平仓和止损的操作
    # loop = asyncio.get_event_loop()
    # loop.run_until_complete(asyncio.wait(task))py3.5
    asyncio.run(task)  # py3.7


def target2():
    print("线程2运行")
    task = [stop_profit(current_price),stop_loss()]
    asyncio.run(task)


def main():
try:
    um_futures_client = UMFutures(key=api_key, secret=api_secret)   
    current_price = um_futures_client.mark_price(symbol=symbol)
    #这里我认为,止盈止损应该贯穿整个市场行情中,如果单线程的话,可能会发生止盈止损不及时的情况所以我用了两个线程,一个线程用来管理订单,一个用来止盈和止损
    t1=Thread(target=target1)
    t2=Thread(target=target2)
    t2.start()
    t1.start()
    #回收两个线程的资源
except Exception as e:
    t1.join()
    t2.join()
    #这里先启动止盈止损的线程,防止出现意外

if __name__ == '__main__':
    main()