写在前面的话
这个小项目是笔者的一个面试题,作者之前在学习量化策略的时候,喜欢实现一些策略,很多并未部署到币安实盘,但是这个项目是部署到币安上的项目,确实给我带来了一些挑战,不过也从中学到了不少东西,先学了python异步编程,以及多线程.可能是因为作者的梯子设置有问题,所以没能真正运行起来,不过整体的逻辑完成了,因为网络的问题,没有进行调试,应该有很多业务上或者技术上的缺陷,但是我觉得随着以后的进步,这些肯定可以解决
自己对于量化交易很感兴趣,接到面试的时候,
我欣喜若狂,因为
我在想这会不会是一生中仅有的机会可以从事这个行业,
希望面试可以通过.
下面是我的项目解析
这里是一些必要的库,和很多需要的全局变量
py
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
这里是止盈部分的代码,这里我参考了币安的文档,主要的逻辑是先获取账户信息,然后对于每一个仓位的盈利进行计算,然后进行止盈
py
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)
)
这里是止损部分的代码,止损是全仓止损,计算全仓的亏损,然后对于所有的订单进行平仓
py
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上面的样例
py
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上面币安给的样例
py
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
))
这里是订单控制,用于实现上下一共一百个订单的实现,作者思考这个函数花了很多的时间,可能还是有错误,但是应该基本上完善了,主要的逻辑就是把价格等分,然后上下价格设置订单,注意要把订单放在一个列表里面方便管理
py
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}")
这里也是非常核心的一段代码,目的是在什么情况下,进行订单的撤销,也就是行情波动的时候进行订单的撤销,业务逻辑上稍微有点复杂
py
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)
回调函数,但是没有用到,
这里是一些回调函数,用于套接字的初始化,
py
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应用的不太熟练
py
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()