Содержание
- Инициализация алгоритма
- Вход в позицию
- Создание стоп-ордера
- Понятие обработчика событий OnOrderEvent
- Отслеживание выполнения
Инициализация алгоритма
В Initialize() мы настраиваем наш алгоритм как обычно, используя self.SetCash(), self.SetStartDate() и self.SetEndDate(). Мы также можем запросить данные об инструменте с помощью метода self.AddEquity().
# Subscribe to IBM with raw, daily data
ibm = self.AddEquity("IBM", Resolution.Daily)
ibm.SetDataNormalizationMode(DataNormalizationMode.Raw)
Вход в позицию
Используя MarketOrder(), мы можем купить определенное количество единиц нашего актива. Рыночный ордер отправляется непосредственно на биржу и тут же исполняется.
# Купить 300 штук акций IBM по текущей рыночной цене
self.MarketOrder("IBM", 300)
Создание стоп-ордера
Обычно мы устанавливаем стоп-лосс так, чтобы он срабатывал ниже цены покупки существующего актива. Разница между ценой актива и ценой стопа состоит в том, сколько мы готовы потерять.
В QuantConnect есть два вида стоп-ордеров: стоп-лимит и стоп-рынок. Стоп-рыночный ордер должен знать количество акций для продажи (или покупки) и цену для срабатывания ордера. Метод StopMarketOrder() имеет следующие аргументы: ticker, quantity, и stopPrice.
# Продать 300 штук акций IBM по текущей рыночной цене, когда цена опуститься ниже 95% текущей цены закрытия
self.StopMarketOrder("IBM", -300, 0.95 * self.Securities["IBM"].Close)
Пример:
class BuyAndHold(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 12, 1) # Set Start Date
self.SetEndDate(2019, 4, 1) # Set End Date
self.SetCash(100000) # Set Strategy Cash
#1. Subscribe to SPY in raw mode
spy = self.AddEquity("SPY", Resolution.Daily)
spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
def OnData(self, data):
if not self.Portfolio.Invested:
#2. Create market order to buy 500 units of SPY
self.MarketOrder("SPY", 500)
#3. Create a stop market order to sell 500 units at 90% of the SPY current price
self.StopMarketOrder("SPY", -500, 0.90 * self.Securities["SPY"].Close )
Понятие обработчика событий OnOrderEvent
События ордера — это обновления статуса вашего ордера. Каждое событие ордера отправляется обработчику событий def OnOrderEvent(), при этом информация о статусе заказа хранится в объекте OrderEvent.
def OnOrderEvent(self, orderEvent):
pass
Объект OrderEvent имеет свойство Status со значениями перечисления OrderStatus Submitted, PartiallyFilled, Filled, Canceled и Invalid. Он также содержит свойство OrderId, которое представляет собой уникальный номер, представляющий ордер.
Отслеживание выполнения
В нашем алгоритме мы хотим отслеживать полные выполнения ордера, чтобы знать, когда сработал наш стоп. Мы можем игнорировать другие события, явно ища статус Filled.
if orderEvent.Status == OrderStatus.Filled:
# Печатать Id ордера
self.Debug(orderEvent.OrderId)
Пример
class BuyAndHold(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 12, 1)
self.SetEndDate(2019, 4, 1)
self.SetCash(100000)
spy = self.AddEquity("SPY", Resolution.Daily)
spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.lastOrderEvent = None
def OnData(self, data):
if not self.Portfolio.Invested:
self.MarketOrder("SPY", 500)
self.StopMarketOrder("SPY", -500, 0.9 * self.Securities["SPY"].Close)
def OnOrderEvent(self, orderEvent):
#1. Write code to only act on fills
if orderEvent.Status == OrderStatus.Filled:
#2. Save the orderEvent to lastOrderEvent, use Debug to print the event OrderId
self.lastOrderEvent = orderEvent
print(self.Debug(self.lastOrderEvent.OrderId))
Срабатывание стоп-лосс
Важно знать, был ли достигнут стоп-лосс, чтобы мы не сразу снова вошли в рынок.
Отслеживание с помощью OrderTicket
При размещении ордера QuantConnect возвращает объект OrderTicket, который можно использовать для обновления свойств ордера, запроса его отмены или получения его OrderId.
# Place our order and return an order ticket
self.stopMarketTicket = self.StopMarketOrder("IBM", -300, ibmStockPrice * 0.9)
# Log its OrderId
self.Debug(self.stopMarketTicket.OrderId)
Определение момента исполнения стоп-ордера
OrderId хранится в параметре orderEvent, переданном в наш метод OnOrderEvent(). Мы можем сопоставить orderEvent.OrderId с идентификатором стоп-рыночного ордера, чтобы увидеть, был ли исполнен наш ордер.
# Check if we hit our stop market
if self.stopMarketTicket is not None and orderEvent.OrderId == self.stopMarketTicket.OrderId:
self.stopMarketFillTime = self.Time;
Управление повторным входом алгоритма
Алгоритм может размещать сотни сделок в секунду, поэтому важно тщательно контролировать, когда он размещает сделки. Задайте себе эти вопросы при отслеживании состояния вашего алгоритма, например:
- Когда я в последний раз размещал сделку?
- Соответствовал ли заказ моим ожиданиям?
- Правильно ли я размещаю заказы?
# Check that at least 15 days (~2 weeks) have passed since we last hit our limit order
if (self.Time - self.stopMarketFillTime).days < 15:
return
Пример:
class BuyAndHold(QCAlgorithm):
# Order ticket for our stop order, Datetime when stop order was last hit
stopMarketTicket = None
stopMarketFillTime = datetime.min
def Initialize(self):
self.SetStartDate(2018, 12, 1)
self.SetEndDate(2019, 4, 1)
self.SetCash(100000)
spy = self.AddEquity("SPY", Resolution.Daily)
spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
def OnData(self, data):
#4. Check that at least 15 days (~2 weeks) have passed since we last hit our stop order
if (self.Time - self.stopMarketFillTime).days < 15:
return
if not self.Portfolio.Invested:
self.MarketOrder("SPY", 500)
#1. Create stop loss through a stop market order
self.stopMarketTicket = self.StopMarketOrder("SPY", -500, self.Securities["SPY"].Close* 0.9)
def OnOrderEvent(self, orderEvent):
if orderEvent.Status != OrderStatus.Filled:
return
# Printing the security fill prices.
self.Debug(self.Securities["SPY"].Close)
#2. Check if we hit our stop loss (Compare the orderEvent.Id with the stopMarketTicket.OrderId)
# It's important to first check if the ticket isn't null (i.e. making sure it has been submitted)
if self.stopMarketTicket is not None and orderEvent.OrderId == self.stopMarketTicket.OrderId:
#3. Store datetime
self.stopMarketFillTime = self.Time;
Создание скользящего стоп-лосса
Обновляя цену срабатывания стопа по мере движения рынка, мы теоретически можем зафиксировать прибыль и ограничить риск убытков. Это превращает наше статическое управление рисками в динамическое.
Обновление ордеров
Ордера, которые не были выполнены немедленно, могут быть обновлены с помощью их тикета ордера. Чтобы обновить ордер, вы создаете объект UpdateOrderFields, который содержит все свойства, которые вы хотите изменить.
Чтобы обновить стоп-цену данного тикета ордера, мы вызываем orderticket.Update().
# Update stop loss price using UpdateOrderFields helper.
updateFields = UpdateOrderFields()
updateFields.StopPrice = self.Securities["SPY"].Close * 0.9
self.stopMarketTicket.Update(updateFields)
Полный пример:
class BuyAndHold(QCAlgorithm):
# Order ticket for our stop order, Datetime when stop order was last hit
stopMarketTicket = None
stopMarketOrderFillTime = datetime.min
highestSPYPrice = 0
def Initialize(self):
self.SetStartDate(2018, 12, 1)
self.SetEndDate(2018, 12, 10)
self.SetCash(100000)
spy = self.AddEquity("SPY", Resolution.Daily)
spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
def OnData(self, data):
if (self.Time - self.stopMarketOrderFillTime).days < 15:
return
if not self.Portfolio.Invested:
self.MarketOrder("SPY", 500)
self.stopMarketTicket = self.StopMarketOrder("SPY", -500, 0.9 * self.Securities["SPY"].Close)
else:
#1. Check if the SPY price is higher that highestSPYPrice.
if self.Securities["SPY"].Close > self.highestSPYPrice:
self.highestSPYPrice = self.Securities["SPY"].Close
#2. Save the new high to highestSPYPrice; then update the stop price to 90% of highestSPYPrice
updateFields = UpdateOrderFields()
updateFields.StopPrice = self.highestSPYPrice * 0.9
self.stopMarketTicket.Update(updateFields)
#3. Print the new stop price with Debug()
print(self.Debug(updateFields.StopPrice))
def OnOrderEvent(self, orderEvent):
if orderEvent.Status != OrderStatus.Filled:
return
if self.stopMarketTicket is not None and self.stopMarketTicket.OrderId == orderEvent.OrderId:
self.stopMarketOrderFillTime = self.Time
Визуализация уровней стоп-лосса
Диаграммы — это мощный способ визуализации поведения вашего алгоритма. Дополнительные сведения об API построения диаграмм
см. в документации.
Создание диаграммы
Метод Plot() может нарисовать линейный график с помощью одной строки кода. Требуется три аргумента, имя графика, имя серии и значение, которое вы хотели бы построить.
# На одной диаграмме может быть несколько графиков.
self.Plot("Levels", "Asset Price", self.Securities["IBM"].Price)
self.Plot("Levels", "Stop Price", self.Securities["IBM"].Price * 0.9)
Пример:
class BuyAndHold(QCAlgorithm):
# Order ticket for our stop order, Datetime when stop order was last hit
stopMarketTicket = None
stopMarketOrderFillTime = datetime.min
highestSPYPrice = -1
def Initialize(self):
self.SetStartDate(2018, 12, 1)
self.SetEndDate(2018, 12, 10)
self.SetCash(100000)
spy = self.AddEquity("SPY", Resolution.Daily)
spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
def OnData(self, data):
# 1. Plot the current SPY price to "Data Chart" on series "Asset Price"
self.Plot("Data Chart", "Asset Price", self.Securities["SPY"].Price)
if (self.Time - self.stopMarketOrderFillTime).days < 15:
return
if not self.Portfolio.Invested:
self.MarketOrder("SPY", 500)
self.stopMarketTicket = self.StopMarketOrder("SPY", -500, 0.9 * self.Securities["SPY"].Close)
else:
#2. Plot the moving stop price on "Data Chart" with "Stop Price" series name
self.Plot("Data Chart", "Stop Price", self.Securities["SPY"].Price * 0.9)
if self.Securities["SPY"].Close > self.highestSPYPrice:
self.highestSPYPrice = self.Securities["SPY"].Close
updateFields = UpdateOrderFields()
updateFields.StopPrice = self.highestSPYPrice * 0.9
self.stopMarketTicket.Update(updateFields)
def OnOrderEvent(self, orderEvent):
if orderEvent.Status != OrderStatus.Filled:
return
if self.stopMarketTicket is not None and self.stopMarketTicket.OrderId == orderEvent.OrderId:
self.stopMarketOrderFillTime = self.Time