robot_bsk
robot_bsk личный блог
18 февраля 2019, 18:59

Как обойтись без склейки фьючей при тестировании и оптимизации торговой стратегии в Wealth-lab

Ответ на комментарий Дмитрия Власова «А как процесс «Перекладки» организован? График эквити в итоге один получается?» в посте «Как обойтись без склейки фьючей при тестировании и оптимизации торговой стратегии в ТСЛаб»
 
 

При тестировании и оптимизации в Wealth-lab 6.9 я раньше использовал склеенный фьючерс.
В коде прописывал даты выхода из всех позиций и даты, когда уже можно было входить (после гепа склейки и нормализации индикаторов).

Сейчас я использую портфель фьючерсов и влд отлично с этим справляется (он может тестировать и оптимизировать портфель инструментов).

Начнем с тиккеров. Нужно было сделать так, чтобы они шли по порядку по алфавиту.
Поэтому пришлось заняться переименовкой: самый первый SiH8 (2008г. выпуска) переименован в SI11, далее SiM8 (2008) ->SI12 ……  SiH9 (2019) ->SI55.

Как обойтись без склейки фьючей при тестировании и оптимизации торговой стратегии в Wealth-lab
Для нефти я начал с числа 111, т.к. экспирируются фьючи каждый месяц.

Как выглядит общее эквити:
Как обойтись без склейки фьючей при тестировании и оптимизации торговой стратегии в Wealth-lab


Как выглядят сделки при переносе:
Как обойтись без склейки фьючей при тестировании и оптимизации торговой стратегии в Wealth-lab

Выход из SI18 и вход в SI19 происходит на закрытии одной и той же свечи, одним и тем же кол-вом контрактов.

Как я это реализовал в коде:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;

namespace Bsk.Strategies
{
	public class Bsk : WealthScript
	{
		private double EquitySize = 0.0;    // Наша эквити
		private double Commiss = 20.0;		// Комиссия на 1 контракт в 2 стороны
		private int znak=0;					// Знак при переносе
		private int posper=0;				// Кол-во контрактов при переносе
		private int firstrun=0;				// Флаг первого запуска
		private	int dataent0 = 0, dataent = 0, dataex = 20200312; // Даты начала и конца фьючерсов	
		private string instr1="";			// Самый первый тиккер в портфеле
		
		protected override void Execute()
		{
			if (firstrun==0)
			{
				instr1 = Bars.Symbol; // SI11
				string Instrument = (string)instr1.Substring(0, 2); // Инструмент SI
				string SymN= (string)instr1.Substring(2, 2);		// Номер Инструмента 11
				int SymInt;
				bool isNum = int.TryParse(SymN, out SymInt); 		// Проверяем является ли string SymN числом
				if (isNum)											// Если SymN число	
				{
					SymInt=Convert.ToInt32(SymN);
					if (SymInt==11) dataent0=20071217;				// Если портфель фьючей начинается с 11
					if (SymInt>11)									// Если >11, например с SI17
					{
						SymInt=SymInt-1;							// например 16
						string instr2=Instrument+SymInt;			// instr2=SI16
						SetContext(instr2, true);
						// Находим дату экпиры предыдущего фьюча например SI16
						dataent0 = Date[Bars.Count-1].Year*10000+Date[Bars.Count-1].Month*100+Date[Bars.Count-1].Day; 
						RestoreContext();
					}
				}

				firstrun=1;
			}
			
			// Если ВЛД лиценция
			PositionSize ps;
			ps = this.GetPositionSize();
			if (EquitySize==0.0) EquitySize=ps.StartingCapital;

			// Если не лицензия
			if (EquitySize==0.0) EquitySize=1000000;
			
			bool isSignalBuy = false, isSignalShort = false; // Сигналы на вход в длинную и короткую позиции
			bool isSignalSell = false, isSignalCover = false; // Сигналы на выход из длинной и короткой позиции 
			Position pos; // Позиция, с которой будем работать
			int data0,data1=0;
			
			// При оптимизации надо сбрасывать до первоначальных значений переменные, если мы снова пришли на самый первый тиккер
			
			// Если ВЛД лиценция
			if (Bars.Symbol == instr1) { dataent = dataent0; EquitySize=ps.StartingCapital; znak=0; posper=0;}
			
			// Если не лицензия
			if (Bars.Symbol == instr1) { dataent = dataent0; EquitySize=1000000; znak=0; posper=0;}
			
			dataex=Date[Bars.Count-1].Year*10000+Date[Bars.Count-1].Month*100+Date[Bars.Count-1].Day;
						
			for (int bar = 20; bar < Bars.Count; bar++)
			{
				bool isLastBar = (bar == Bars.Count - 1);	
				
				data0 = Date[bar].Year*10000+Date[bar].Month*100+Date[bar].Day;
				if (!isLastBar) // Если не последний бар
					data1 = Date[bar+1].Year*10000+Date[bar+1].Month*100+Date[bar+1].Day;
				if (data0 >= dataent && data1 < dataex) 
				{	
					if (data0 == dataent&&!IsLastPositionActive)
					{
						if (znak==1) // Перенос позиции БАЙ с предыдущего фьюча
						{
							SetShareSize((int)(posper));
							pos = BuyAtClose(bar-1, "Buyperenos"); 
						}
						if (znak==-1) // Перенос позиции ШОРТ с предыдущего фьюча
						{
							SetShareSize((int)(posper));
							pos = ShortAtClose(bar-1, "Shortperenos");
						}
					}

					isSignalBuy = (ххххх);   // Задаем условие для входа в лонг
					isSignalCover = (ххххх); // Задаем условие для выхода из шорта
					isSignalShort = (ххххх); // Задаем условие для входа в шорт
					isSignalSell = (ххххх);  // Задаем условие для выхода из лонга

					if (IsLastPositionActive) // Если позиция есть
					{
						pos = LastActivePosition; // Последняя активная позиция
						// Если получили сигнал на выход из длинной позиции и поза лонговая
						if (isSignalSell && LastPosition.PositionType == PositionType.Long ) 
						{
							// считаем эквити
							EquitySize = EquitySize + pos.NetProfitAsOfBar(bar) - Commiss*pos.Shares;
							SellAtClose(bar , pos, ""+EquitySize); // то выйти из позиции
						}
						// Если получили сигнал на выход из короткой позиции и поза шортовая
						if (isSignalCover && LastPosition.PositionType == PositionType.Short ) 
						{
							// считаем эквити
							EquitySize = EquitySize + pos.NetProfitAsOfBar(bar) - Commiss*pos.Shares;
							CoverAtClose(bar , pos, ""+EquitySize); // то выйти из позиции
						}						
					} // IsLastPositionActive
					if (!IsLastPositionActive) // Если позиции нет
					{
						if (isSignalBuy) // Если пришел сигнал на покупку
						{
							Lot=xxxxx; //задаем кол-во контрактов (манименеджмент)
							SetShareSize((int)(Lot));
							pos = BuyAtClose(bar, "Buy"); // Пробуем войти в длинную позицию

						}
						if (isSignalShort) // Если пришел сигнал на короткую продажу
						{
							Lot=xxxxx; //задаем кол-во контрактов (манименеджмент)
							SetShareSize((int)(Lot));
							pos = ShortAtClose(bar, "Short");
						}
					} // !IsLastPositionActive		

				} //data if (data0 >= dataent && data1 < dataex) 
				else
				{
					if (IsLastPositionActive) // Если поза есть и мы знаем что завтра экпира
					{
						// считаем эквити
						EquitySize = EquitySize + LastPosition.NetProfitAsOfBar(bar) - Commiss*LastPosition.Shares;
						posper=(int)(LastPosition.Shares);   						// Запоминаем кол-во контрактов
						znak=-1;													// Запоминаем направление шорт
						if (LastPosition.PositionType == PositionType.Long) znak=1; // Запоминаем направление лонг
						ExitAtClose(bar , Position.AllPositions, ""+EquitySize); 	// Выходим из всех позиций
						dataent=dataex;												// Дата начала для следующего фьюча = дата экспиры текущего									
					}
				} //data
			} // for
		}
	}
}

P.S. Более подробно я рассказываю на курсе Портфель роботов на Qlua под ключ
2 Комментария
  • Павел Дуков
    06 апреля 2019, 14:18
    Спасибо!
  • Beach Bunny
    18 декабря 2023, 00:43
    Есть способ намного лучше!
    Соединять фьючерсы в один — но по ДРУГОМУ, не так тупо как все делают.
    По портфелю фьючерсов тестирование идет намного медленнее, чем по склеенному. Технология не особо сложная, но так никто не делает, хотя подобная методика много где используется, не в трейдинге.
    Обо все видах склеек которые используют для трейдинга я к курсе, но такое чувство что все их делали люди знакомые максимум с арифметикой.
    ps
    Сама методика — ноухау, публиковаться не будет, ибо #удаков на Smart-Labe как собак.

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн