淺談比特幣現(xiàn)貨做市策略
這篇文章是上一篇文章淺談比特幣期貨做市策略的火幣現(xiàn)貨版本。現(xiàn)貨的做市策略肯尺,跟期貨做市很類似,復(fù)雜性甚至還不如期貨做市策略(因為期貨做市策略還要考慮鎖倉肃续、移倉霹肝、對手盤減倉等復(fù)雜的期貨相關(guān)的處理邏輯)。
現(xiàn)貨做市策略主要有以下幾個模塊:
1、短期趨勢判斷模塊:判斷短期的趨勢荠列,如果呈現(xiàn)極端行情类浪,暫停做市;如果單邊趨勢較為明顯弯予,減少做市訂單的委托數(shù)量戚宦。
2、做市模塊(使用做市算法下單功能):分析買賣盤口信息锈嫩,如果價差滿足條件受楼,利用做市算法下單功能,在上下買賣N檔進(jìn)行批量下單
3呼寸、再平衡模塊(使用再平衡算法下單功能):分析當(dāng)前持倉比例與初始持倉比例的差異(凈頭寸)艳汽,如果超過一定限額,啟動再平衡功能对雪。再平衡功能啟動后河狐,系統(tǒng)利用再平衡算法下單功能,對凈頭寸進(jìn)行消化處理(即到市場去對沖掉該部分凈頭寸瑟捣。如果凈頭寸為多馋艺,則下達(dá)賣單;反之迈套,則下達(dá)買單捐祠,直到凈頭寸減少到可承受的大小)
4桑李、主循環(huán):負(fù)責(zé)粘合1踱蛀,2,3的邏輯并使之能夠持續(xù)輪詢執(zhí)行贵白。其中率拒,1和2是順序處理模塊,3是獨立的線程禁荒。
下面逐一介紹各個模塊的代碼構(gòu)成猬膨。
一、短期趨勢判斷模塊
使用如下函數(shù)判斷短期趨勢:
# 價格趨勢系數(shù)
def price_trend_factor(self, trades, buy1_price, sell1_price, buy2_price, sell2_price, buy3_price, sell3_price, vol_list, index_type=None, symmetric=True):
prices = trades["price"].values.tolist()
latest_trades = prices[-6:]
mid_price = (buy1_price+sell1_price)/2*0.7 + (buy2_price+sell2_price)/2*0.2 + (buy3_price+sell3_price)/2*0.1
latest_trades.append(mid_price)
is_bull_trend = False
is_bear_trend = False
last_price_too_far_from_latest = False
has_large_vol_trade = False
if latest_trades[-1] > max(latest_trades[:-1]) + latest_trades[-1]*0.00005 or (latest_trades[-1] > max(latest_trades[:-2]) + latest_trades[-1]*0.00005 and latest_trades[-1] > latest_trades[-2]):
is_bull_trend = True
elif latest_trades[-1] < min(latest_trades[:-1]) - latest_trades[-1]*0.00005 or (latest_trades[-1] < min(latest_trades[:-2]) - latest_trades[-1]*0.00005 and latest_trades[-1] < latest_trades[-2]):
is_bear_trend = True
if abs(latest_trades[-1] - latest_trades[-2]*0.7 - latest_trades[-3]*0.2 - latest_trades[-4]*0.1) > latest_trades[-1]*0.002:
last_price_too_far_from_latest = True
if max(vol_list) > 20:
has_large_vol_trade = True
if is_bull_trend or is_bear_trend or last_price_too_far_from_latest or has_large_vol_trade:
return 0
if index_type == "rsi":
prices = trades["price"]
index = indicators.rsi_value(prices, len(prices)-1)
else:
index = self.buy_trades_ratio(trades)
# 價格趨勢嚴(yán)重呛伴,暫停交易
if index <= 20 or index >= 80:
return 0
# 對稱下單時勃痴,factor用來調(diào)整下單總數(shù)
if symmetric:
factor = 1 - abs(index-50)/50
# 非對稱下單時,factor用來調(diào)整買入訂單的數(shù)量
else:
factor = index / 50
return factor
我們看到磷蜀,如果是明顯的上漲召耘、下跌行情,或前后成交價格相差較大褐隆,或者盤口堆積了大數(shù)量的委托單(is_bull_trend or is_bear_trend or last_price_too_far_from_latest or has_large_vol_trade)污它, 則返回0(表示暫停做市)。另外,考慮最近的買賣成交比例衫贬,如果某個方向上的主動成交數(shù)量明顯大于另一個方向德澈,則表明買賣力量開始失衡,價格趨勢將要開始固惯,這時需要對做市的委托訂單量進(jìn)行壓縮處理梆造。
二、做市模塊(使用做市算法下單功能)
做市模塊的代碼如下:
def trade_thread(self):
while True:
try:
if self.timeInterval > 0:
self.timeLog("Trade - 等待 %d 秒進(jìn)入下一個循環(huán)..." % self.timeInterval)
time.sleep(self.timeInterval)
# 檢查order_info_list里面還有沒有pending的order葬毫,然后cancel他們
order_id_list = []
for odr in self.order_info_list:
order_id_list.append(odr["order_id"])
self.huobi_cancel_pending_orders(order_id_list=order_id_list)
self.order_info_list = []
account = self.get_huobi_account_info()
buy1_price = self.get_huobi_buy_n_price()
sell1_price = self.get_huobi_sell_n_price()
buy2_price = self.get_huobi_buy_n_price(n=2)
sell2_price = self.get_huobi_sell_n_price(n=2)
buy3_price = self.get_huobi_buy_n_price(n=3)
sell3_price = self.get_huobi_sell_n_price(n=3)
buy1_vol = self.get_huobi_buy_n_vol()
sell1_vol = self.get_huobi_sell_n_vol()
buy2_vol = self.get_huobi_buy_n_vol(n=2)
sell2_vol = self.get_huobi_sell_n_vol(n=2)
buy3_vol = self.get_huobi_buy_n_vol(n=3)
sell3_vol = self.get_huobi_sell_n_vol(n=3)
buy4_vol = self.get_huobi_buy_n_vol(n=4)
sell4_vol = self.get_huobi_sell_n_vol(n=4)
buy5_vol = self.get_huobi_buy_n_vol(n=5)
sell5_vol = self.get_huobi_sell_n_vol(n=5)
vol_list = [buy1_vol,buy2_vol,buy3_vol,buy4_vol,buy5_vol,sell1_vol,sell2_vol,sell3_vol,sell4_vol,sell5_vol]
latest_trades_info = self.get_latest_market_trades()
# 賬戶或者行情信息沒有取到
if not all([account, buy1_price, sell1_price]):
continue
self.heart_beat_time.value = time.time()
global init_account_info
if init_account_info is None:
init_account_info = account
global account_info_for_r_process
account_info_for_r_process = copy.deepcopy(self.account_info)
min_price_spread = self.arbitrage_min_spread(self.get_huobi_buy_n_price(), self.min_spread_rate)
# 計算下單數(shù)量
total_qty = min(self.total_qty_per_transaction, account.btc, account.cash / buy1_price)
trend_factor = self.price_trend_factor(latest_trades_info, buy1_price, sell1_price, buy2_price, sell2_price, buy3_price, sell3_price, vol_list, symmetric=self.is_symmetric)
if self.is_symmetric:
total_qty *= trend_factor
buy_ratio = 1
sell_ratio = 1
else:
buy_ratio = trend_factor
sell_ratio = 2-trend_factor
order_data_list = self.orders_price_and_qty_from_min_spread(buy1_price, sell1_price, total_qty,
self.price_step, self.qty_step,
self.min_qty_per_order,
self.max_qty_per_order,
min_price_spread, buy_ratio=buy_ratio,
sell_ratio=sell_ratio)
self.spot_batch_limit_orders(self.market_type, order_data_list, time_interval_between_threads=self.time_interval_between_threads)
current_spread = self.bid_ask_spread(self.exchange)
self.save_transactions(signal_spread=current_spread, signal_side="market_maker")
self.latest_trade_time = time.time()
except Exception:
self.timeLog(traceback.format_exc())
continue
其中镇辉,做市算法下單模塊的代碼如下:
# 從最小價差向外掛單
def orders_price_and_qty_from_min_spread(self, buy1_price, sell1_price, total_qty, price_step, qty_step,
min_qty_per_order, max_qty_per_order, min_price_spread, buy_ratio=1, sell_ratio=1):
orders_list = []
remaining_qty = total_qty
avg_price = (buy1_price + sell1_price) / 2
if buy_ratio > 1: # price is going down
avg_price += 0.2
elif sell_ratio > 1: # price is going up
avg_price -= 0.2
buy_order_price = avg_price - min_price_spread / 2
sell_order_price = avg_price + min_price_spread / 2
order_qty = min(min_qty_per_order, remaining_qty)
while remaining_qty >= min_qty_per_order and buy_order_price > buy1_price and sell_order_price < sell1_price:
#buy_order_qty = max(order_qty * buy_ratio, self.min_order_qty)
#sell_order_qty = max(order_qty * sell_ratio, self.min_order_qty)
buy_order_qty = max(order_qty, self.min_order_qty)
sell_order_qty = max(order_qty, self.min_order_qty)
orders_list.append({"price": buy_order_price, "amount": buy_order_qty, "type": "buy"})
orders_list.append({"price": sell_order_price, "amount": sell_order_qty, "type": "sell"})
remaining_qty -= buy_order_qty
buy_order_price -= price_step
sell_order_price += price_step
order_qty = min(buy_order_qty + qty_step, max_qty_per_order)
order_qty = min(remaining_qty, order_qty)
return orders_list
我們看到,做市模塊首先檢查order_info_list里面還有沒有pending的order贴捡,然后cancel它們忽肛。然后,拿盤口的信息+賬戶信息+最近成交信息烂斋,計算出來以下幾項:
(1)滿足做市條件的最小價差min_price_spread
(2)最多能下達(dá)的訂單數(shù)量total_qty
(3)當(dāng)前趨勢因子值(用短期趨勢判斷模塊算出來)
(4)批量做市買賣委托單列表(利用做市算法下單模塊)
然后屹逛,將計算出來的做市買賣委托單列表,通過批量下單接口汛骂,直接下到市場中去罕模。下單的方式是類似IOC(immediate or cancel),一定時限之后帘瞭,所有未完成的掛單都將被撤銷淑掌。
對于做市算法下單模塊,其基本原理就是按照一定的價格和下單數(shù)量步長图张,從最小價差逐漸往外掛成對買賣單锋拖,直到剩余未掛單量很小诈悍,或者最外面的掛單已經(jīng)觸碰到了當(dāng)前盤口的買一賣一價祸轮。(注意:我們的做市掛單都是插入在盤口買一賣一價格之間的)
三、再平衡模塊(使用再平衡算法下單功能)
再平衡模塊代碼如下:
def go(self):
while True:
try:
if self.timeInterval > 0:
self.timeLog("R-balance - 等待 %d 秒進(jìn)入下一個循環(huán)..." % self.timeInterval)
time.sleep(self.timeInterval)
# 檢查order_info_list里面還有沒有pending的order侥钳,然后cancel他們
order_id_list = []
for odr in self.order_info_list:
order_id_list.append(odr["order_id"])
self.huobi_cancel_pending_orders(order_id_list=order_id_list)
self.order_info_list = []
global init_account_info
account_info = self.get_huobi_account_info_1(max_delay=self.account_info_max_delay)
buy_1_price = self.get_huobi_buy_n_price()
sell_1_price = self.get_huobi_sell_n_price()
if not all([account_info, init_account_info, buy_1_price, sell_1_price]):
continue
self.heart_beat_time.value = time.time()
qty_delta = account_info.btc_total - init_account_info.btc_total
cash_delta = account_info.cash_total - init_account_info.cash_total
# 需要賣出
if qty_delta >= self.min_order_qty:
trade_type = helper.SPOT_TRADE_TYPE_SELL
order_qty = qty_delta
if cash_delta <= 0:
holding_avg_price = abs(cash_delta/qty_delta)
else:
holding_avg_price = None
init_price = sell_1_price
if holding_avg_price is None:
worst_price = buy_1_price
else:
worst_price = max(buy_1_price, holding_avg_price * (1+self.mim_spread_rate))
#worst_price = buy_1_price
# 需要買入
elif qty_delta <= -self.min_order_qty:
trade_type = helper.SPOT_TRADE_TYPE_BUY
order_qty = -qty_delta
if cash_delta > 0:
holding_avg_price = abs(cash_delta/qty_delta)
# 錢與幣都減少适袜,賣出的均價為負(fù)
else:
holding_avg_price = None
init_price = buy_1_price
if holding_avg_price is None:
worst_price = sell_1_price
else:
worst_price = min(sell_1_price, holding_avg_price * (1-self.mim_spread_rate))
#worst_price = sell_1_price
# 無需操作
else:
continue
# 下單限價單
res = self.spot_order_to_target_qty(self.market_type, self.coin_type, trade_type, order_qty, init_price,
price_step=self.price_step, worst_price=worst_price,
max_qty_per_order=self.qty_per_order, max_time=self.max_time)
if res is None:
total_executed_qty = 0
else:
total_executed_qty, deal_avg_price = res
remaining_qty = order_qty - total_executed_qty
# 若設(shè)置了參數(shù)MARKET_ORDER_WHEN_QTY_DIFF_TOO_LARGE 為True,則可能需要市價單補單
if remaining_qty >= self.min_order_qty and self.use_market_order:
current_diff_ratio = remaining_qty / init_account_info.btc_total
if self.max_qty_per_market_order is not None:
order_qty = min(remaining_qty, self.max_qty_per_market_order)
else:
order_qty = remaining_qty
order_id = None
# 市價賣出
if trade_type == helper.SPOT_TRADE_TYPE_SELL and current_diff_ratio > self.max_positive_diff_ratio:
order_id = self.spot_order(self.market_type, self.coin_type, trade_type,
helper.ORDER_TYPE_MARKET_ORDER, quantity=order_qty)
# 市價買入
elif trade_type == helper.SPOT_TRADE_TYPE_BUY and current_diff_ratio > self.max_negative_diff_ratio:
cash_amount = sell_1_price * order_qty
order_id = self.spot_order(self.market_type, self.coin_type, trade_type,
helper.ORDER_TYPE_MARKET_ORDER, cash_amount=cash_amount)
if order_id is not None:
self.spot_order_wait_and_cancel(self.market_type, self.coin_type, order_id)
self.save_transactions(signal_side="rebalance")
self.latest_trade_time = time.time()
except Exception:
self.timeLog(traceback.format_exc())
continue
再平衡模塊需要計算當(dāng)前凈頭寸的持倉成本舷夺,然后以不劣于該成本價的方式將該凈頭寸拋入市場苦酱,從而即達(dá)到了平衡頭寸的目的,又控制了再平衡成本给猾。計算凈頭寸持倉成本的邏輯如下:
qty_delta = account_info.btc_total - init_account_info.btc_total
cash_delta = account_info.cash_total - init_account_info.cash_total
# 需要賣出
if qty_delta >= self.min_order_qty:
trade_type = helper.SPOT_TRADE_TYPE_SELL
order_qty = qty_delta
if cash_delta <= 0:
holding_avg_price = abs(cash_delta/qty_delta)
else:
holding_avg_price = None
init_price = sell_1_price
if holding_avg_price is None:
worst_price = buy_1_price
else:
worst_price = max(buy_1_price, holding_avg_price * (1+self.mim_spread_rate))
# 需要買入
elif qty_delta <= -self.min_order_qty:
trade_type = helper.SPOT_TRADE_TYPE_BUY
order_qty = -qty_delta
if cash_delta > 0:
holding_avg_price = abs(cash_delta/qty_delta)
# 錢與幣都減少疫萤,賣出的均價為負(fù)
else:
holding_avg_price = None
init_price = buy_1_price
if holding_avg_price is None:
worst_price = sell_1_price
else:
worst_price = min(sell_1_price, holding_avg_price * (1-self.mim_spread_rate))
# 無需操作
else:
continue
算出了凈頭寸持倉成本后,用再平衡算法下單功能將該頭寸拋入市場:
# 下單限價單
res = self.spot_order_to_target_qty(self.market_type, self.coin_type, trade_type, order_qty, init_price,
price_step=self.price_step, worst_price=worst_price,
max_qty_per_order=self.qty_per_order, max_time=self.max_time)
if res is None:
total_executed_qty = 0
else:
total_executed_qty, deal_avg_price = res
其中敢伸,spot_order_to_target_qty的算法如下:
def spot_order_to_target_qty(self, marketType, coinType, trade_type, target_qty, init_order_price, price_step=None,
worst_price=None, max_qty_per_order=None, max_time=None):
"""
交易目標(biāo)數(shù)量的標(biāo)的扯饶,不停的下單、撤單、補單(補單時將價格向不利方向小幅移動)尾序,直至全部成交或價格達(dá)到某一條件或超過一定時間退出
:param marketType: 1: huobi, 2: okcoin
:param coinType: 1: btc, 2: ltc
:param trade_type: helper.SPOT_TRADE_TYPE_BUY or helper.SPOT_TRADE_TYPE_SELL
:param target_qty: 成交的目標(biāo)數(shù)量
:param init_order_price: 最初的下單價格
:param price_step: 每次補單的價格變動钓丰,默認(rèn) 0.5元
:param worst_price: 最不利的價格
:param max_qty_per_order: 每次下單的最大數(shù)量, 默認(rèn)0.005個
:param max_time: 最大執(zhí)行時間每币, 默認(rèn) 60秒
:return:
"""
if price_step is None:
price_step = 0.5
if max_qty_per_order is None:
max_qty_per_order = 0.005
if max_time is None:
max_time = 60
if marketType == helper.HUOBI_MARKET_TYPE:
min_order_qty = helper.HUOBI_BTC_MIN_ORDER_QTY
elif marketType == helper.OKCOIN_MARKET_TYPE:
min_order_qty = helper.OKCOIN_BTC_MIN_ORDER_QTY
else:
return None
if trade_type == helper.SPOT_TRADE_TYPE_SELL:
price_step *= -1
total_executed_qty = 0
total_deal_cash_amount = 0
remaining_qty = target_qty - total_executed_qty
start_time = time.time()
end_time = start_time + max_time
order_price = init_order_price
if trade_type == helper.SPOT_TRADE_TYPE_BUY:
if worst_price is None:
worst_price = init_order_price * 1.1
order_price = min(order_price, worst_price)
else:
if worst_price is None:
worst_price = init_order_price * 0.9
order_price = max(order_price, worst_price)
while True:
order_qty = min(remaining_qty, max_qty_per_order)
if order_qty < min_order_qty:
break
order_id = self.spot_order(marketType, coinType, trade_type, helper.ORDER_TYPE_LIMIT_ORDER, price=order_price,
quantity=order_qty)
if order_id is None:
continue
self.spot_order_wait_and_cancel(marketType, coinType, order_id)
res = self.spot_order_info_detail(marketType, coinType, order_id)
if res is None:
continue
else:
executed_qty = res[1]
avg_price = res[2]
total_executed_qty += executed_qty
total_deal_cash_amount += executed_qty * avg_price
remaining_qty = target_qty - total_executed_qty
order_price += price_step
if remaining_qty < min_order_qty:
self.timeLog("剩余未成交數(shù)量(%.4f)小于交易所最小下單數(shù)量(%.4f)" % (remaining_qty, min_order_qty))
break
if time.time() > end_time:
self.timeLog("超過了最大執(zhí)行時間携丁,停止繼續(xù)下單")
break
if trade_type == helper.SPOT_TRADE_TYPE_BUY:
if order_price > worst_price:
self.timeLog("當(dāng)前買入下單價格(%.2f元)大于最差價格(%.2f元)" % (order_price, worst_price))
break
else:
if order_price < worst_price:
self.timeLog("當(dāng)前賣出下單價格(%.2f元)小于最差價格(%.2f元)" % (order_price, worst_price))
break
if total_executed_qty > 0:
deal_avg_price = total_deal_cash_amount / total_executed_qty
else:
deal_avg_price = 0
return total_executed_qty, deal_avg_price
四、主循環(huán)
負(fù)責(zé)粘合1兰怠,2梦鉴,3的邏輯并使之能夠持續(xù)輪詢執(zhí)行。其中揭保,1和2是順序處理模塊尚揣,3是獨立的線程。代碼比較簡單掖举,如下:
def go(self):
self.timeLog("日志啟動于 %s" % self.getStartRunningTime().strftime(self.TimeFormatForLog))
self.timeLog("開始cancel pending orders")
self.huobi_cancel_pending_orders()
self.timeLog("完成cancel pending orders")
thread_pool = []
thread_pool.append(Thread(target=self.trade_thread, args=()))
if self.need_rebalance:
spot_rebalance = HuobiSpotRebalance(self.heart_beat_time, self.coinMarketType, depth_data=self.depth_data,
transaction_info=self.order_info_queue)
thread_pool.append(Thread(target=spot_rebalance.go, args=()))
for thread in thread_pool:
thread.setDaemon(True)
thread.start()
for thread in thread_pool:
thread.join()
五快骗、做市效果分析
我們看到,做市策略在頻繁地進(jìn)行買賣操作塔次,每次成交的買賣方篮,都有一定的利潤。利潤能夠穩(wěn)定地積少成多励负,最后累積成比較平穩(wěn)向上的一條資金曲線:
Source by: WeQuant-Jason