Стратегия "Купи и держи" со скользящим стопом.

Содержание

Инициализация алгоритма

В 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;

Управление повторным входом алгоритма

Алгоритм может размещать сотни сделок в секунду, поэтому важно тщательно контролировать, когда он размещает сделки. Задайте себе эти вопросы при отслеживании состояния вашего алгоритма, например:
  1. Когда я в последний раз размещал сделку?
  2. Соответствовал ли заказ моим ожиданиям?
  3. Правильно ли я размещаю заказы?
# 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