Для построения прибыльного биржевого робота недостаточно разработать только алгоритм с положительным математическим ожиданием определения будущего движения цены. Чтобы не истратить это преимущество на проскальзывание, комиссию и т.п. при входе в сделку и закрытии позиций, необходимо предусмотреть механизм правильного выставления и исполнения ордеров. Подобный механизм в упрощенном виде представлен в блоге Investment Idiocy, здесь привожу перевод этой статьи.
Автор использует полностью автоматические системы для торговли на бирже. Сначала использовался простейший процесс исполнения:
- проверить, что на лучшем биде (если собираетесь продавать) или на лучшем аске (при покупке) есть достаточно большой объем ордеров, чтобы удовлетворить вашу заявку;
- послать маркет-ордер (или лимитную заявку с ценой, равной цене лучшего бида/аска)
Вот так, очень примитивно. Но затем автор решил проверить, во сколько ему обходится такой алгоритм исполнения. Большую часть времени стоимость такой постановки заявок составила половину спреда (разницы между лучшим аском и лучшим бидом).
После пары месяцев такой торговли было принято решение об уменьшении потерь при исполнении ордеров и для этого необходимо было сделать правильный алгоритм выставления заявок.
Автор признает, что не является экспертом в создании подобных алгоритмов, поэтому должен руководствоваться простыми принципами. Так как разработка в основном ведется на языке Python, который является достаточно медленным, то и алгоритм не будет содержать особо сложного кода, который необходим для исполнения при высокочастотной торговле. Также выставляемые ордера не содержат большого объема, поэтому не нужно разбивать их на малые части и затем управлять каждой. В связи со всем этим, алгоритм должен быть несложным.
Нужно упомянуть одну важную деталь - перестановка ордеров может быть небесплатна. Некоторые биржи взимают плату за транзакции, или за превышение порога транзакций. Это не очень большая плата, и преимущества интеллектуального исполнения должны превысить эти затраты.
Хороший ручной трейдер, желающий исполнить небольшой ордер на покупку и не бепокоящийся о своем влиянии на рынок, вероятно поступит так:
- отправит лимитный ордер на ту сторону спреда, где он хочет торговать, добавив его на текущий уровень. Если мы покупаем, то это текущий лучший бид. По биржевой терминологии это пассивное поведение, мы ждем. когда кто-то возьмет наш ордер.
- в идеальных условиях этот начальный ордер будет исполнен. Мы выиграем половину спреда, то есть стоимость исполнения становится отрицательной ( лучший бид минус средняя цена).
- но если:
- ордер не будет исполнен в течение нескольких минут
- или есть предположение, что рынок пойдет в сторону от вашей заявки и далее
- или рынок уже двинулся в эту сторону
... то умный трейдер будет ограничивать свои убытки и модифицирует свою заявку - понесет затраты на исполнение и пересечет спред. Это агрессивное поведение.
Новый ордер купит актив по лучшему аску. В теории он исполнится, стоимость исполнения составит половину спреда ( если рынок уже двигался некоторое время в другую сторону от заявки, то и более).
Если вы слишком медленный и рынок продолжает движение, продолжайте перестановку ордера, пытаясь исполнится на лучшем аске, пока весь ордер не исполнится.
Хотя алгоритм выглядит просто, есть много подводных камней при его реализации, и получить правильное исполнение можно, если учесть все эти особенности. Ниже рассмотрим стратегию детально на языке программирования. Хотя мы представим все на языке Python, код не будет исполняться, так как автор не включил некоторые необходимые подпрограммы. Но это все равно даст вам основную идею, как создать самостоятельно подобный алгоритм.
Подготовительный этап перед сделкой
Очень опасно торговать с помощью автоматических алгоритмов, если рынок недостаточно ликвиден. Следующая программа проверяет это:
pretrademultiplier=4.0 def EasyAlgo_pretrade(ctrade, contract, dbtype, tws): """ Функция ЕasуАlgo запускается перед отправкой нового ордера ctrade: планируемая сделка, знаковая целая переменная contract: актив, который мы торгуем dbtype, tws: обозначает базу данных и API, которые используем Функция возвращает значение объема, который безопасен для торговли Ноль означает, что биржа не поддерживает этот вид ордера """ ## Получаем маркет-дату (список, содержащий спред и объемы) bookdata=get_market_data(dbtype, tws, contract, snapshot=False, maxstaleseconds=5, maxwaitseconds=5) ## None означает, что API не запущен и рынок закрыт if bookdata is None: return (0, bookdata) ## Проверка, ликвиден ли рынок, спред и объем должен быть в рамках определенных лимитов. Мы используем множитель, так как менее требовательны при использовании лимитных ордеров - широкий спред играет нам на руку market_liquid=check_is_market_liquid(bookdata, contract.code, multiplier=pretrademultiplier) if not market_liquid: return (0, bookdata) ## Если рынок ликвиден, но наш ордер слишком велик в сравнении с объемами на концах спреда, мы можем урезать его до нужного объема cutctrade=cut_down_trade_to_order_book(bookdata, ctrade, multiplier=pretrademultiplier) return (cutctrade, bookdata)
Новый ордер
Следующая программа нужна для постановки нового ордера:
MAX_DELAY=0.03 def EasyAlgo_new_order(order, tws, dbtype, use_orderid, bookdata): """ Эта функция запускается для получения нового ордера Args: order - тип ордера, нужный для сделки tws - объект для связи с API Interactive Brokers dbtype - используемая база данных use_orderid- идентификационный номер ордера bookdata- список, содержащий лучший бид и аск и их объемы """ ## s, состояние, переменная, используемая для логгирования и диагностики log=logger() diag=diagnostic(dbtype, system="algo", system3=str(order.orderid)) s=state_from_sdict(order.orderid, diag, log) ## Из стакана и сделок получаем цену по которой мы ставим агрессивные (sideprice) и пассивные ордера (offsideprice) (sideprice, offsideprice)=get_price_sides(bookdata, order.submit_trade) if np.isnan(offsideprice) or offsideprice==0: log.warning("No offside / limit price in market data so can't issue the order") return None if np.isnan(sideprice) or sideprice==0: log.warning("No sideprice in market data so dangerous to issue the order") return None ## объект order содержит цену, установленную при генерации ордера, проверка - есть ли большое движение цены с этого момента (должно быть менее, чем за 1 сек) if not np.isnan(order.submit_price): delay=abs((offsideprice/order.submit_price) - 1.0) if delay>MAX_DELAY: log.warning("Large move since submission - not trading a limit order on that") return None ## Если мы удовлетворены состоянием стакана, устанавливаем цену лимит ордера 'offside' - лучший аск при продаже, лучший бид при покупке limitprice=offsideprice ## меняем объект ордер - сейчас это лимитный ордер с нужной ценой order.modify(lmtPrice = limitprice) order.modify(orderType="LMT") ## необходима трансляция нашего объекта в объекты API iborder=from_myorder_to_IBorder(order) contract=Contract(code=order.code, contractid=order.contractid) ibcontract=make_IB_contract(contract) ## диагностический блок s.update(dict(limit_price=limitprice, offside_price=offsideprice, side_price=sideprice, message="StartingPassive", Mode="Passive")) timenow=datetime.datetime.now() ## таблица сохранения алгоритма используется для сохранения информации состояний. Важно - пременная Mode=PASSIVE в начальном состоянии! am=algo_memory_table(dbtype) am.update_value(order.orderid, "Limit", limitprice) am.update_value(order.orderid, "ValidSidePrice", sideprice) am.update_value(order.orderid, "ValidOffSidePrice", offsideprice) am.update_value(order.orderid, "Trade", order.submit_trade) am.update_value(order.orderid, "Started", date_as_float(timenow)) am.update_value(order.orderid, "Mode", "Passive") am.update_value(order.orderid, "LastNotice", date_as_float(timenow)) am.close() ## Отправляем ордер tws.placeOrder( use_orderid, # orderId, ibcontract, # contract, iborder # order ) ## Возвращает поток ордеров для сохранения в базе данных. Если программа завершается раньше, возвращает None; таким образом, подпрограмма знает, что ни один ордер не был выставлен return order
В следующей части рассмотрим действия при обновлении стакана и сделок, отраженные в соответствующих подпрограммах на языке Python.
Сообщение