Продолжение. Первая часть см. здесь.
Действия по тику
Тик получаем из API, когда какая-то часть стакана обновляется ( лучший бид или аск, либо их объемы). Внутри кода взаимодействия с API (tws) есть подпрограмма, которая следит за приходом такого обновления и вызывает соответсвующую функцию.
Что делает такая функция? Если мы выставляем пассивный ордер ( помните, это состояние по умолчанию):
- ... и прошло более 5 минут - меняем ордер на агрессивный.
- если мы покупаем и текущий лучший бид уходит вверх от стартового состояния, меняем ордер на агрессивный
- если продаем, и текущий лучший аск уходит вниз - также меняем на агрессивный
-
если мы видим нежелательный дисбаланс ордеров ( например, если количество ордеров на продажу в 5 раз превышает количество ордеров на покупку, в случае, когда мы также продаем) - меняем на агрессивный
Если мы выставляем агрессивный ордер
- ... и более 10 минут проходит, отменяем ордер
- если покупаем и текущий лучший аск движется вверх от стартового состояния, обновляем цену нашего лимитного ордера на равную новому лучшему аску ( движемся за рынком)
- если продаем и текущий лучший бид движется вниз, обновляем цену нашего ордера на равную лучшему биду.
passivetimelimit=5*60 ## макс 5 мин
totaltimelimit=10*60 ## макс другие 5 мин для агрессивного состояния
maximbalance=5.0 ## величина дисбаланса ордеров
def EasyAlgo_on_tick(dbtype, orderid, marketdata, tws, contract):
"""
Эта функция вызывается на каждый тик
Args:
dbtype, tws: обозначают базу данных и API
orderid: идентификационный номер, ассоциированный с тиком
marketdata: текущая маркет дата (стакан)
contract: контракт, который мы торгуем
"""
## Диагностический код
log=logger()
diag=diagnostic(dbtype, system="algo", system3=str(int(orderid)))
s=state_from_sdict(orderid, diag, log)
## Достаем информацию о нашем ордере из базы данных
am=algo_memory_table(dbtype)
trade=am.read_value(orderid, "Trade")
current_limit=am.read_value(orderid, "Limit")
Started=am.read_value(orderid, "Started")
Mode=am.read_value(orderid, "Mode")
lastsideprice=am.read_value(orderid, "ValidSidePrice")
lastoffsideprice=am.read_value(orderid, "ValidOffSidePrice")
LastNotice=am.read_value(orderid, "LastNotice")
## если не находим ордер в базе данных
if Mode is None or Started is None or current_limit is None or trade is None or LastNotice is None:
log.critical("Can't get algo memory values for orderid %d CANCELLING" % orderid)
FinishOrder(dbtype, orderid, marketdata, tws, contract)
Started=float_as_date(Started)
LastNotice=float_as_date(LastNotice)
timenow=datetime.datetime.now()
## если покупка, получаем лучший аск (sideprice) и лучший бид (offsideprice)
## если продажа, получаем лучший бид (sideprice) и лучший аск (offsideprice)
(sideprice, offsideprice)=get_price_sides(marketdata, trade)
s.update(dict(limit_price=current_limit, offside_price=offsideprice, side_price=sideprice,
Mode=Mode))
## получаем время, сколько мы торгуем и время с последнего уведомления 'LastNotice'
time_trading=(timenow - Started).total_seconds()
time_since_last=(timenow - LastNotice).seconds
## если прошла минута
if time_since_last>60:
s.update(dict(message="One minute since last noticed now %s, total time %d seconds - waiting %d %s %s" % (str(timenow), time_trading, orderid, contract.code, contract.contractid)))
am.update_value(orderid, "LastNotice", date_as_float(timenow))
## если мы вышли за максимальное время - сбрасываем ордера
if time_trading>totaltimelimit:
s.update(dict(message="Out of time cancelling for %d %s %s" % (orderid, contract.code, contract.contractid)))
FinishOrder(dbtype, orderid, marketdata, tws, contract)
return -1
if not np.isnan(sideprice) and sideprice<>lastsideprice:
am.update_value(orderid, "ValidSidePrice", sideprice)
if not np.isnan(offsideprice) and offsideprice<>lastoffsideprice:
am.update_value(orderid, "ValidOffSidePrice", offsideprice)
am.close()
if Mode=="Passive":
## время вышло для пассивного ордера (5 минут)
if time_trading>passivetimelimit:
s.update(dict(message="Out of time moving to aggressive for %d %s %s" % (orderid, contract.code, contract.contractid)))
SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade)
return -1
if np.isnan(offsideprice):
s.update(dict(message="NAN offside price in passive mode - waiting %d %s %s" % (orderid, contract.code, contract.contractid)))
return -5
if trade>0:
## Покупка
if offsideprice > current_limit:
## с момента постановки ордера цена ушла вверх
s.update(dict(message="Adverse price move moving to aggressive for %d %s %s" % (orderid, contract.code, contract.contractid)))
SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade)
return -1
elif trade<0:
## Selling
if offsideprice < current_limit: ## с момента постановки ордера цена ушла вниз
s.update(dict(message="Adverse price move moving to aggressive for %d %s %s" % (orderid, contract.code, contract.contractid)))
SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade)
return -1
## Обнаружен дисбаланс (объем бид/объем аск если покупаем, наоборот - если продаем)
balancestat=order_imbalance(marketdata, trade)
if balancestat > maximbalance:
s.update(dict(message="Order book imbalance of %f developed compared to %f, switching to aggressive for %d %s %s" %(balancestat , maximbalance, orderid, contract.code, contract.contractid)))
SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade)
return -1
elif Mode=="Aggressive":
## агрессивный ордер
if np.isnan(sideprice):
s.update(dict(message="NAN side price in aggressive mode - waiting %d %s %s" % (orderid, contract.code, contract.contractid)))
return -5
if trade>0:
## Покупка
if sideprice > current_limit:
## с момента постановки ордера цена ушла вверх. Переставляем ордер
s.update(dict(message="Adverse price move in aggressive mode for %d %s %s" % (orderid, contract.code, contract.contractid)))
SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade)
return -1
elif trade<0:
## Selling
if sideprice < current_limit ## с момента постановки ордера цена ушла вниз. Переставляем ордер
s.update(dict(message="Adverse price move in aggressive mode for %d %s %s" % (orderid, contract.code, contract.contractid)))
SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade)
return -1
elif Mode=="Finished":
## ничего не делаем
pass
else:
msg="Mode %s not known for order %d" % (Mode, orderid)
s.update(dict(message=msg))
log=logger()
log.critical(msg)
raise Exception(msg)
s.update(dict(message="tick no action %d %s %s" % (orderid, contract.code, contract.contractid)))
diag.close()
return 0
def SwitchToAggresive(dbtype, orderid, marketdata, tws, contract, trade):
"""
действия при смене текущего ордера на агрессивный или передвижение агрессивного ордера
"""
## diagnostics...
log=logger()
diag=diagnostic(dbtype, system="algo", system3=str(int(orderid)))
s=state_from_sdict(orderid, diag, log)
if tws is None:
log.info("Switch to aggressive didn't get a tws... can't do anything in orderid %d" % orderid)
return -1
## Получаем последнюю лучшую цену (равную цене при пересечении спреда) которая будет ценой нашего нового ордера
am=algo_memory_table(dbtype)
sideprice=am.read_value(orderid, "ValidSidePrice")
ordertable=order_table(dbtype)
order=ordertable.read_order_for_orderid(orderid)
ordertable.close()
if np.isnan(sideprice):
s.update(dict(message="To Aggressive: Can't change limit for %d as got nan - will try again" % orderid))
return -1
## обновляем ордер
newlimit=sideprice
order.modify(lmtPrice = newlimit)
order.modify(orderType="LMT")
iborder=from_myorder_to_IBorder(order)
ibcontract=make_IB_contract(contract)
am.update_value(order.orderid, "Limit", newlimit)
am.update_value(order.orderid, "Mode", "Aggressive")
am.close()
tws.placeOrder(
orderid, # orderId,
ibcontract, # contract,
iborder # order
)
s.update(dict(limit_price=newlimit, side_price=sideprice,
message="NowAggressive", Mode="Aggresive"))
return 0
def FinishOrder(dbtype, orderid, marketdata, tws, contract):
"""
алгоритм не сработал, отменяем ордер
""" diag=diagnostic(dbtype, system="algo", system3=str(int(orderid)))
s=state_from_sdict(orderid, diag, log) log=logger()
if tws is None:
log.info("Finish order didn't get a tws... can't do anything in orderid %d" % orderid)
return -1
log=logger()
ordertable=order_table(dbtype)
order=ordertable.read_order_for_orderid(orderid)
log.info("Trying to cancel %d because easy algo failure" % orderid)
tws.cancelOrder(int(order.brokerorderid))
order.modify(cancelled=True)
ordertable.update_order(order)
do_order_completed(dbtype, order)
EasyAlgo_on_complete(dbtype, order, tws)
s.update(dict(message="NowCancelling", Mode="Finished"))
am=algo_memory_table(dbtype)
am.update_value(order.orderid, "Mode", "Finished")
am.close()
return -1
Частичное или полное сведение ордеров
def EasyAlgo_on_partial(dbtype, order, tws):
"""
Функция запускается при частичном сведении ордера
"""
diag=diagnostic(dbtype, system="algo", system3=str(int(order.orderid)))
diag.w(order.filledtrade, system2="filled")
diag.w(order.filledprice, system2="fillprice")
return 0
def EasyAlgo_on_complete(dbtype, order_filled, tws):
"""
Функция запускается при полном сведении ордера
"""
diag=diagnostic(dbtype, system="algo", system3=str(int(order_filled.orderid)))
diag.w("Finished", system2="Mode")
diag.w(order_filled.filledtrade, system2="filled")
diag.w(order_filled.filledprice, system2="fillprice")
am=algo_memory_table(dbtype)
am.update_value(order_filled.orderid, "Mode", "Finished")
am.close()
return 0
Заключение
Представленный алгоритм не является идеальным, но при его использовании автором затраты на исполнение ордеров снизились на 80% по сравнению с выставлением только маркет ордеров, то есть в среднем плата за исполение составила только 1/10 спреда. Это определенно серьезное улучшение торгового алгоритма и рекомендуется к применению для алгоритмической торговли.



Получается фактически главным параметром исполнения ордера является время удержания ордера.
Не понятный момент
Если мы выставляем агрессивный ордер
... и более 10 минут проходит, отменяем ордер
Если агрессивный ордер это, например, выставление ордера на покупку по ask, то как он может быть не исполнен в течении 10 минут?
это бывает очень часто. Ваши ордера попадают на биржу с некоторой задержкой, и может быть так, что ask уже поднялся выше и ваша заявка стала в обычную бидовую очередь