S#.Shell. Manual
Atom Reply
10/14/2013


Часть 1. Starting up


Первым делом неплохо было бы разобраться с коннекторами.

Уже есть Альфадирект и Квик. Мне бы хотелось подрубить свой кастомный для IB. Но незадача: В каркасе старая версия S#.
Это грозит проблемами в работе с гидрой и коннекторами. У меня возникли сразу, потому что коннектор был на 4.1.19.1.

Значит заодно и обновлюсь.

Удаляю все, что связано со stocksharp и Ecng из reference, и добавляю заново все свежее.
У меня вот такой список получился.

1


После обновления, ничего не заработает.

Поправил в ConnectionEngine для квика и для альфа аналогично.

2


После ребилда все должно взлететь.

Добавил, естественно, нужные Refercences, и создание коннектора по аналогии.

3

4


Настройки. Тут есть целый менеджер настроек, который отвечает за сохранение, и т.д.

Добавил в список коннекторов и нужные настройки в SettingsProperties


5

6


Ну вот и все.


7

7







27 Answers
1 2  >
Kazai Mazai

Avatar
Articles author
Date: 10/21/2013
Reply


Часть 2. Почва для удобного добавления стратегий.

В прошлый раз я немного поторопился с обновлением версии S#. Нашлись кое-какие неполадки в коннекторе и пришлось откатиться. Тем не менее, там все актуально за исключением парочки нюансов при создании новых подключений. Главное делать по аналогии, и все будет хорошоThumpUp


Добавил базовые свойства стратегии.

Код
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using StockSharp.BusinessEntities;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;

namespace Robot
{
  public class BaseStrategyProperties:INotifyPropertyChanged
	{
		private string _name = "Новая стратегия";
		private string _security = "TEST@TEST";
		private string _portfolio = "TEST_PORTFOLIO";
        private decimal _volume = 1;

        private Dictionary<long, Order> _ordersByTransactionId = new Dictionary<long, Order>();
        private Dictionary<long, Security> _securitiesByTransactionId = new Dictionary<long, Security>();
        private Dictionary<long, Portfolio> _portfoliosByTransactionId = new Dictionary<long, Portfolio>();
        private Dictionary<long, IEnumerable<Trade>> _tradesByTransactionId = new Dictionary<long, IEnumerable<Trade>>();
        private Dictionary<long, Exchange> _exchangesByTransactionId = new Dictionary<long, Exchange>();

		private bool _schedulerIsEnabled;
		private List<TimeRangeProperties> _schedule = new List<TimeRangeProperties>(10);
 

		[DisplayName(@"Название")]
		[Description(@"Название стратегии")]
		[Category(@"Основные")]
		//[CategoryOrder(0)]
		[PropertyOrder(0)]
		public string Name
		{
			get { return _name; }
			set
			{
				_name = value;
				OnPropertyChanged("Name");
			}
		}

		[DisplayName(@"Инструмент")]
		[Description(@"Код торгового инструмента")]
		[Category(@"Основные")]
		//[CategoryOrder(0)]
		[PropertyOrder(1)]
		[Editor(typeof(SecurityIdEditor), typeof(SecurityIdEditor))]
		public string Security
		{
			get { return _security; }
			set
			{
				_security = value;
				OnPropertyChanged("Security");
			}
		}

		[DisplayName(@"Портфель")]
		[Description(@"Имя портфеля")]
		[Category(@"Основные")]
		//[CategoryOrder(0)]
		[PropertyOrder(3)]
		[Editor(typeof(PortfolioNameEditor), typeof(PortfolioNameEditor))]
		public string Portfolio
		{
			get { return _portfolio; }
			set
			{
				_portfolio = value;
				OnPropertyChanged("Portfolio");
			}
		}

        [DisplayName(@"Объем")]
        [Description(@"Объем для открытия позиций")]
        [Category(@"Основные")]
        //[CategoryOrder(0)]
        [PropertyOrder(4)]
        public decimal Volume
        {
            get { return _volume; }
            set
            {
                if (value < 0)
                {
                    return;
                }

                _volume = value;
                OnPropertyChanged("Volume");
            }
        }

 
 

		[Category(@"Планировщик")]
		//[CategoryOrder(5)]
		[DisplayName(@"Включен")]
		[Description(@"Включен ли запуск по расписанию для данной стратегии.")]
		[PropertyOrder(0)]
		public bool SchedulerIsEnabled
		{
			get { return _schedulerIsEnabled; }
			set
			{
				_schedulerIsEnabled = value;
				OnPropertyChanged("SchedulerIsEnabled");
			}
		}

		[Category(@"Планировщик")]
		//[CategoryOrder(5)]
		[DisplayName(@"Время работы")]
		[Description(@"Настройка времени автоматической работы стратегии.")]
		[PropertyOrder(1)]
		[Editor(typeof(ScheduleEditor), typeof(ScheduleEditor))]
		public List<TimeRangeProperties> Schedule
		{
			get { return _schedule; }
			set
			{
				_schedule = value;
				OnPropertyChanged("Schedule");
			}
		}

        [DisplayName(@"ОрдерПоАйдиТранзакции")]
        [Description(@"ОрдерПоАйдиТранзакции")]
        [Category(@"Для работы")]
        [PropertyOrder(0)]
        public Dictionary<long, Order> OrdersByTransactionId
        {
            get { return _ordersByTransactionId; }
            set
            {
                _ordersByTransactionId = value;
                OnPropertyChanged("OrdersByTransactionId");
            }
        }

        [DisplayName(@"ИнструментПоАйдиТранзакции")]
        [Description(@"ИнструментПоАйдиТранзакции")]
        [Category(@"Для работы")]
        [PropertyOrder(0)]
        public Dictionary<long, Security> SecuritiesByTransactionId
        {
            get { return _securitiesByTransactionId; }
            set
            {
                _securitiesByTransactionId = value;
                OnPropertyChanged("SecuritiesByTransactionId");
            }
        }

        [DisplayName(@"СделкиПоАйдиТранзакции")]
        [Description(@"СделкиПоАйдиТранзакции")]
        [Category(@"Для работы")]
        [PropertyOrder(0)]
        public Dictionary<long, IEnumerable<Trade>> TradesByTransactionId
        {
            get { return _tradesByTransactionId; }
            set
            {
                _tradesByTransactionId = value;
                OnPropertyChanged("TradesByTransactionId");
            }
        }

        [DisplayName(@"ПортфелиПоАйдиТранзакции")]
        [Description(@"ПортфелиПоАйдиТранзакции")]
        [Category(@"Для работы")]
        [PropertyOrder(0)]
        public Dictionary<long, Portfolio> PortfoliosByTransactionId
        {
            get { return _portfoliosByTransactionId; }
            set
            {
                _portfoliosByTransactionId = value;
                OnPropertyChanged("PortfoliosByTransactionId");
            }
        }
        [DisplayName(@"БиржиПоАйдиТранзакции")]
        [Description(@"БиржиПоАйдиТранзакции")]
        [Category(@"Для работы")]
        [PropertyOrder(0)]
        public Dictionary<long, Exchange> ExchangesByTransactionId
        {
            get { return _exchangesByTransactionId; }
            set
            {
                _exchangesByTransactionId = value;
                OnPropertyChanged("ExchangesByTransactionId");
            }
        }

       
		public event PropertyChangedEventHandler PropertyChanged;

		public void OnPropertyChanged(string name)
		{
			var handler = PropertyChanged;
			if (handler != null)
			{
				handler(this, new PropertyChangedEventArgs(name));
			}
		}
	}
}



и базовую стратегию, восстанавливающую ордера,сделки и позиции при перезапуске.

Код
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using StockSharp.Algo;
using StockSharp.BusinessEntities;

namespace Robot.Base
{
   public class BaseSavingStatesStrategy: BaseStrategy
    {
       public BaseStrategyProperties Params { set; get; }
       private bool _isFirstStart;

       public BaseSavingStatesStrategy()
       {
           _isFirstStart = true;

       }

       protected void SaveStateToParams()
       {

           foreach (var order in Orders)
           {
               var transactionId = order.TransactionId;
               if (Params.OrdersByTransactionId.ContainsKey(transactionId))
               {
                   Params.OrdersByTransactionId[transactionId] = order;
               }
               else
               {
                   Params.OrdersByTransactionId.Add(transactionId, order);
               }

               var myTrades = order.GetTrades();
               List<Trade> trades = new List<Trade>();
               foreach (var myTrade in myTrades)
               {
                   trades.Add(myTrade.Trade);
               }
               if (Params.TradesByTransactionId.ContainsKey(transactionId))
               {
                   Params.TradesByTransactionId[transactionId] = trades;
               }
               else
               {
                   Params.TradesByTransactionId.Add(transactionId, trades);
               }

               if (Params.SecuritiesByTransactionId.ContainsKey(transactionId))
               {
                   Params.SecuritiesByTransactionId[transactionId] = order.Security;
               } 
               else
               {
                   Params.SecuritiesByTransactionId.Add(transactionId, order.Security);
               }
               if (Params.ExchangesByTransactionId.ContainsKey(transactionId))
               {
                   Params.ExchangesByTransactionId[transactionId] = order.Security.ExchangeBoard.Exchange;
               }
               else
               {
                   Params.ExchangesByTransactionId.Add(transactionId, order.Security.ExchangeBoard.Exchange);
               }
               if (Params.PortfoliosByTransactionId.ContainsKey(transactionId))
               {
                   Params.PortfoliosByTransactionId[transactionId] = order.Portfolio;
               }
               else
               {
                   Params.PortfoliosByTransactionId.Add(transactionId, order.Portfolio);

               }
              
           }
       }

       protected void LoadStateFromParams()
       {
               foreach (var transactionId in Params.OrdersByTransactionId.Keys)
           {
               var order = Params.OrdersByTransactionId[transactionId];
               if (order != null)
               {
                   order.Trader = Trader;
                   var security = Params.SecuritiesByTransactionId[transactionId];
                   if (security != null)
                   {

                       var exchange = Params.ExchangesByTransactionId[transactionId];
                       if (exchange != null) security.ExchangeBoard.Exchange = exchange;

                       security.Trader = Trader;
                       order.Security = security;

                   }

                   var portfolio = Params.PortfoliosByTransactionId[transactionId];
                   if (portfolio != null)
                   {
                       Portfolio.Trader = Trader;
                       order.Portfolio = portfolio;
                   }

                   var myTrades = new List<MyTrade>();
                   var trades = Params.TradesByTransactionId[transactionId];

                   if (trades != null)
                       foreach (var trade in trades)
                       {
                           var myTrade = new MyTrade();
                           myTrade.Order = order;
                           myTrade.Trade = trade;
                           myTrades.Add(myTrade);

                       }
                   if (myTrades.Count > 0)
                   {
                       AttachOrder(order, myTrades);
                   }
                    
               }
           }

       }

       protected  override  void OnStarted()
       {
           base.OnStarted();

         if(_isFirstStart)
         {
             _isFirstStart = false;
             LoadStateFromParams();

         }

       }

       protected override void OnStopped()
       {
           SaveStateToParams();
           
           base.OnStopped();
       }

      
    }
}





Правильней было бы сохранять все в бд, используя EntityRegistry, но как-то с базой гидры не хотело взлетать. А так работает, ну и окей. Не так уж часто стратегии перезапускаются в самом деле.




Немного поменял добавление стратегий.
Теперь, когда нужно будет включить новую стратегию, можно просто сделать для нее команду добавления, приписать в xml и она появится в менюшке добавления. Главное - все по аналогии.ThumpUp

1







list.png 42 KB (0)
Topic starter
Thanks: Bond

Kazai Mazai

Avatar
Articles author
Date: 10/24/2013
Reply


Часть 3.1. Добавление своих стратегий.

Эта история полна нюансов.

В шелле хорошо показано, на примере одной стратегии как все работает и т.д.

Добавить свою взамен существующей тоже легко - меняем да и все. Либо по-варварски вставляем в существующую новые внутренности.

Но тут не так все гибко, чтобы наряду с существующими в два щелчка добавить новые стратегии и чтобы все прекрасно работало и сохранялось.

Можно чуть менее чем полностью переделать SettingsEngine, половину кода связанного с добавлением стратегий, и только потом добавлять какие угодно новые стратегии в два щелчка.

А можно сделать минимум изменений, прикрутив маленькие костыли и затем добавлять новые стратегии не в 2 а в 6 щелчков, о чем и пойдет речь дальше.

Я уже подготовил небольшую почву, наподобие той, про которую было во 2-ой части. Когда мне удастся подружиться с ТФС, эти изменения уже будут в шелле.

Может показаться, что кода много, но на самом деле там 90% копи паст.


Добавляем заготовку для стратегии, для параметров и параметров тестирования, которые когда-нибудь наполним содержанием.

Код


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Robot.Strategies
{
    public class HustleEveryDayStrategyProperties :BaseShellStrategyProperties
    {
        

    }

    public class  HustleEveryDayStrategyTestingProperties:BaseShellTestingProperties
    {
        

    }


   public class HustleEveryDayStrategy:BaseShellStrategy
    {


    }
}



Идем в SettingsEngine, добавляем методы для сохранения и загрузки параметров для нового типа стратегий.

Код

private const string HustleEveryDayStrategies = @"HustleEveryDayStrategies.xml";
private const string TestingHustleEveryDayStrategies = @"TestingHustleEveryDayStrategies.xml";

        public void SaveHustleEveryDayStrategiesProperties(List<HustleEveryDayStrategyProperties> properties)
        {
            this.AddInfoLog("Сохранение стратегий в {0}", HustleEveryDayStrategies);
            using (var writer = new StreamWriter(HustleEveryDayStrategies, false))
            {
                var mySerializer = new XmlSerializer(typeof(List<HustleEveryDayStrategyProperties>));
                mySerializer.Serialize(writer, properties);
            }
        }


        public List<HustleEveryDayStrategyProperties> LoadHustleEveryDayStrategiesProperties()
        {
            this.AddInfoLog("Загрузка стратегий из {0}", HustleEveryDayStrategies);

            var result = new List<HustleEveryDayStrategyProperties>();

            if (File.Exists(HustleEveryDayStrategies))
            {
                try
                {
                    using (var reader = new StreamReader(HustleEveryDayStrategies))
                    {
                        var x = new XmlSerializer(typeof(List<HustleEveryDayStrategyProperties>));
                        result = (List<HustleEveryDayStrategyProperties>)x.Deserialize(reader);
                    }
                }
                catch (Exception ex)
                {
                    this.AddErrorLog("Ошибка загрузки {0}: {1}", HustleEveryDayStrategies, ex.Message);
                }
            }

            return result;
        }

        public void SaveTestingHustleEveryDayStrategyProperties(List<HustleEveryDayStrategyTestingProperties> properties)
        {
            this.AddInfoLog("Сохранение тестовых стратегий в {0}", TestingHustleEveryDayStrategies);
            using (var writer = new StreamWriter(TestingHustleEveryDayStrategies, false))
            {
                var mySerializer = new XmlSerializer(typeof(List<HustleEveryDayStrategyTestingProperties>));
                mySerializer.Serialize(writer, properties);
            }
        }

        public List<HustleEveryDayStrategyTestingProperties> LoadTestingHustleEveryDayStrategyProperties()
        {
            this.AddInfoLog("Загрузка стратегий тестирования из {0}", TestingHustleEveryDayStrategies);

            var result = new List<HustleEveryDayStrategyTestingProperties>();

            if (File.Exists(TestingStrategiesXml))
            {
                using (var reader = new StreamReader(TestingHustleEveryDayStrategies))
                {
                    var x = new XmlSerializer(typeof(List<HustleEveryDayStrategyTestingProperties>));
                    result = (List<HustleEveryDayStrategyTestingProperties>)x.Deserialize(reader);
                }
            }

            return result;
        }




Идем в Main.

Добавляем маленький костыль.

Код
 

private List<HustleEveryDayStrategy> _hustleEveryDayStrategies = new List<HustleEveryDayStrategy>();

private List<HustleEveryDayStrategy> _hustleEveryDayTestingStrategies = new List<HustleEveryDayStrategy>();
	


И почти как под копирку методы для добавления стратегий:

Код
 
private void AddHustleEveryDayStrategy()
        {
            var properties = new HustleEveryDayStrategyProperties
            {
                // TODO: check if the stategy with same does exist
                Name = "Новая стратегия {0}".Put(_newStrategyCount++),
            };

            AddHustleEveryDayStrategy(properties);
            SaveStrategies();
        }

        private void AddHustleEveryDayStrategy(HustleEveryDayStrategyProperties properties)
		{
			var strategy = new HustleEveryDayStrategy
			{
				Params = properties,
				Trader = ConnectionEngine.Instance.Trader
			};

			_logManager.Sources.Add(strategy);

		    var doc = new LayoutDocument
		    {
                Title = strategy.Params.Name,
                Content = new StrategyDocument
				{
					Strategy = strategy
				},
		        CanClose = false
		    };

			strategy.Params.PropertyChanged += (s, a) =>
			{
				if (a.PropertyName == "Name")
				{
					doc.Title = strategy.Params.Name;
				}

				SaveStrategies();
			};

			_documents.Add(strategy, doc);
                        _hustleEveryDayStrategies.Add(strategy);

			this.AddInfoLog("Добавлена стратегия '{0}'", strategy.Name);

			StrategiesDocumentPane.Children.Add(doc);
		}

        private void AddHustleEverydayTestingStrategy()
        {
            var properties = new HustleEveryDayStrategyTestingProperties
            {
                Name = "Тестирование {0}".Put(_newTestingCount++)
            };

            AddTestingStrategy(properties);
            SaveTestingStrategies();
        }

        private void AddHustleEverydayTestingStrategy(HustleEveryDayStrategyTestingProperties properties)
        {
            var strategy = new HustleEveryDayStrategy
            {
                Params = properties,
                Trader = ConnectionEngine.Instance.Trader
            };

            var doc = new LayoutDocument()
            {
                Title = strategy.Params.Name,
                Content = new TestingDocument
                {
                    Strategy = strategy
                },
                CanClose = false
            };

            strategy.Params.PropertyChanged += (s, a) =>
            {
                if (a.PropertyName == "Name")
                {
                    doc.Title = strategy.Params.Name;
                }

                SaveTestingStrategies();
            };

            _testingDocuments.Add(strategy, doc);
            _hustleEveryDayTestingStrategies.Add(strategy);

            this.AddInfoLog("Добавлена тестовая стратегия '{0}'", strategy.Name);

            StrategiesDocumentPane.Children.Add(doc);
        }





Нужно добавить в InitializeConfiguration() - методе восстановления из настроек.

Код
var hustleProperties = SettingsEngine.Instance.LoadHustleEveryDayStrategiesProperties();

            foreach (var property in hustleProperties)
            {
                AddHustleEveryDayStrategy(property);
            }


            var testingHustleProperties = SettingsEngine.Instance.LoadTestingHustleEveryDayStrategyProperties();

            foreach (var properties in testingHustleProperties)
            {
                AddTestingStrategy(properties);
            }





Ну вот. Теперь сохранение настроек стратегий будет работать не правильно, а удаление будет удалять только из интерфейса, а при перезапуске все будет по-прежнему. И это только на первый взгляд.

Как починить это, а также о том, как добавить кнопки добавления стратегии в интерфейс, узнаем в следующей подчасти части 3.
Topic starter
Thanks: Bond

Bond

Avatar
Training
Date: 10/25/2013
Reply


Прям триллер какой-то! Ждем продолжения! ThumpUp
Thanks:

Kazai Mazai

Avatar
Articles author
Date: 11/4/2013
Reply


Переделать метод сохранения, который в MainWindow.xaml.cs можно как-нибудь в таком духе:

Код
private void SaveStrategies()
		{
			

		    SettingsEngine.Instance.SaveHustleEveryDayStrategiesProperties(
		        _hustleEveryDayStrategies.Select(s => (HustleEveryDayStrategyProperties)s.Params).ToList());

            var otherStrategies = _documents.Keys.Where( s=>! _hustleEveryDayStrategies.Any(str=> str.Params.Name==s.Params.Name)).Select(strategy => strategy.Params).ToList();
            SettingsEngine.Instance.SaveStrategies(otherStrategies);
		}



Сохранять сначала наши новые кастомные стратегии, а потом фильтровать все, например по имени.
Но по идее, если уж пилить стратегии, то для каждого вашего типа стратегий будет свой массив, который и следует сохранять по отдельности, и ничего уже потом не фильтровать.

Аналогично с сохранением свойств тестовых стратегий:

Код
  private void SaveTestingStrategies()
        {
            SettingsEngine.Instance.SaveTestingHustleEveryDayStrategyProperties(
                _hustleEveryDayTestingStrategies.Select(s => (HustleEveryDayStrategyTestingProperties)s.Params).ToList());

            var strategies = _testingDocuments.Keys.Where( s=>! _hustleEveryDayTestingStrategies.Any(str=> str.Params.Name==s.Params.Name)).Select(strategy => (BaseShellTestingProperties)strategy.Params).ToList();

            SettingsEngine.Instance.SaveTestingStrategies(strategies);
        }




В методе
Код
private void ExecutedRemoveStrategy(object sender, ExecutedRoutedEventArgs e)


Там где у нас обработка нажатого "да"

Код
if (result == MessageBoxResult.Yes)
			{
				this.AddInfoLog("Удаление стратегии '{0}'", strategy.Params.Name);

				var doc = _documents[strategy];
				_documents.Remove(strategy);

				StrategiesDocumentPane.RemoveChild(doc);
			    _hustleEveryDayStrategies.RemoveAll(s => s.Params.Name == strategy.Params.Name);
				SaveStrategies();
			}



Удаляем еще и из нашего списка стратегий с фильтрацией по имени. Лучше, конечно, что б у всех стратегий был какой-то id, и все сравнения проводить по нему.



Что бы можно было было из интерфейса добавить свежих стратегий:

Добавляем команду там же в MainWindow.xaml.cs,

Код
public static RoutedCommand AddHustleStrategyCommand = new RoutedCommand();



далее методы для команды.

Код
 private void ExecutedAddHustleStrategy(object sender, ExecutedRoutedEventArgs e)
        {
            AddHustleEveryDayStrategy();

            AllStrategies.ListViewStrategies.ItemsSource = _documents.Keys.ToList();
        }
        private void CanExecuteAddHuslteStrategy(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
            e.Handled = true;
        }




Нужно теперь подключить команду к gui. В MainWindow.xaml:


Там есть такая штука, комманд биндинг. Добавляем вот что:

Код
<CommandBinding Command="{x:Static Robot:MainWindow.AddHustleStrategyCommand}"
                    Executed="ExecutedAddHustleStrategy"
                    CanExecute="CanExecuteAddHustleStrategy" />


далее в нем же ищем "AddStrategyCommand"? находим его в контексте меню итемс, и аналогично добавляем

Код
   <MenuItem Header="Добавить хассол" Command="{x:Static Robot:MainWindow.AddHustleStrategyCommand}"/>




Ну вот, теперь все готово.





Topic starter
Thanks: Bond

Kazai Mazai

Avatar
Articles author
Date: 11/5/2013
Reply


Небольшое лирическое отступление...


Там выше в стратегии реализовывались методы для сохранения ордеров, позиций и т.д.

У меня так раньше было реализовано, не на s# shell, но все же. Возможно версия s# была другая, возможно я где-то наошибался, но недавно на s# shell уже обнаружил, что такой способ не работает. Т.е. если положить Dictionary с ордерами (то что в базовых Properties) в SettingsStorage и попытаться сериализовать, ничего не получится. Если положить SettingsStorage с ордерами в SettingsStorage, тоже не заработало, хотя раньше все прокатывало, если мне не изменяет память.

Во-вторых это, на мой взгляд, не совсем корректно, если в стратегию ордера и сделки поступают не только из Trader, но еще и напрямую из хранилища.

Может возникнуть какая-нибудь дурацкая ситуация, если после перезапуска один и тот же ордер прийдет в стратегию и шлюза и из хранилища, и бог его знает как-там оно все разбираться внутри будет.

Поэтому, в Properties стратегии лучше хранить просто список orderIDs, брать их от Trader'a и привязывать.

Если торговый терминал не передает историю ордеров, прийдется "усовершенствовать" Trader и реализовать возможность сохранения в бд.

Вот такие вот рекомендации.Smile


Topic starter
Thanks:

dij1

Avatar
Training
Date: 11/6/2013
Reply


Вопрос немного не в тему. Удалось ли Вам поймать устойчивую конфигурацию работы с Plaza 2, т.е. версию, которая с плазой пашет?
Thanks:

Kazai Mazai

Avatar
Articles author
Date: 11/6/2013
Reply


dij1 Перейти
Вопрос немного не в тему. Удалось ли Вам поймать устойчивую конфигурацию работы с Plaza 2, т.е. версию, которая с плазой пашет?


Я плазой вообще дел не имел
Topic starter
Thanks:

Kazai Mazai

Avatar
Articles author
Date: 11/18/2013
Reply


По просьбам телезрителей(как минимум одного) сделал зарисовочку для сопровождения позиции.
Открывается прямо при старте, и выходит в какое-то время, а также по стопам и нескольким ценовым тейкам, передвигая стоп в б\у.
Есть кое-какие лишние проверки, потому что кусочки взяты из стратегии на нескольких инструментах, но хуже не будет.


Код
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Logging;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;

namespace Robot.Strategies
{
    public class HustleEveryDayStrategyProperties :BaseShellStrategyProperties
    {

        private decimal _takeProfit1 = 10000000;
        [DisplayName(@"1-й тейк профит")]
        [Description(@"Цена для 1-й цели")]
        [Category(@"Параметры")]
        [PropertyOrder(0)]
        public decimal TakeProfit1
        {
            get { return _takeProfit1; }
            set
            {
                _takeProfit1 = value;
                OnPropertyChanged("TakeProfit1");
            }
        }



        private decimal _takeProfit2 = 10000000;
        [DisplayName(@"2-й тейк профит")]
        [Description(@"Цена для 2-й цели")]
        [Category(@"Параметры")]
        [PropertyOrder(0)]
        public decimal TakeProfit2
        {
            get { return _takeProfit2; }
            set
            {
                _takeProfit2 = value;
                OnPropertyChanged("TakeProfit2");
            }
        }
        private decimal _takeProfit3 = 10000000;
        [DisplayName(@"3-й тейк профит")]
        [Description(@"Цена для 3-й цели")]
        [Category(@"Параметры")]
        [PropertyOrder(0)]
        public decimal TakeProfit3
        {
            get { return _takeProfit3; }
            set
            {
                _takeProfit3 = value;
                OnPropertyChanged("TakeProfit3");
            }
        }

        private decimal _stoploss = 10000000;
        [DisplayName(@"стоп лосс")]
        [Description(@"Цена для стоп лосса")]
        [Category(@"Параметры")]
        [PropertyOrder(0)]
        public decimal Stoploss
        {
            get { return _stoploss; }
            set
            {
                _stoploss = value;
                OnPropertyChanged("Stoploss");
            }
        }
        private DateTime _closeTime = new DateTime(2000,1,1,1,1,1);
        [DisplayName(@"Время закрытия")]
        [Description(@"Время закрытия сделки")]
        [Category(@"Параметры")]
        [PropertyOrder(0)]
        public DateTime CloseTime
        {
            get { return _closeTime; }
            set
            {
                _closeTime = value;
                OnPropertyChanged("CloseTime");
            }
        }

        private OrderDirections _orderDirection = OrderDirections.Buy;
        [DisplayName(@"Направление сделки")]
        [Description(@"Направление сделки")]
        [Category(@"Параметры")]
        [PropertyOrder(0)]
        public OrderDirections OrderDirection
        {
            get { return _orderDirection; }
            set
            {
                _orderDirection = value;
                OnPropertyChanged("OrderDirection");
            }
        }


    }

    public class  HustleEveryDayStrategyTestingProperties:BaseShellTestingProperties
    {
        

    }


   public class HustleEveryDayStrategy:BaseShellStrategy
    {
       protected override void OnStarted()
       {

           base.OnStarted();

           var currentTime = Trader.GetMarketTime(Security.ExchangeBoard.Exchange);
           this.AddInfoLog("Текущее рыночное время: {0}.".Put(currentTime));

           var currentClosePositionsTime = ((HustleEveryDayStrategyProperties) Params).CloseTime;
           this.AddInfoLog("Текущее время проверки выхода по тайм-стопу: {0}.".Put(currentClosePositionsTime));
           if (currentTime > currentClosePositionsTime)
           {
               var newClosePositionsTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day,
                                                        currentClosePositionsTime.Hour, currentClosePositionsTime.Minute,
                                                        currentClosePositionsTime.Second);
               this.AddInfoLog("Новое время проверки выхода по тайм-стопу: {0}.".Put(newClosePositionsTime));
               ((HustleEveryDayStrategyProperties) Params).CloseTime = newClosePositionsTime;
           }

           
           Security
             .WhenTimeCome(((HustleEveryDayStrategyProperties)Params).CloseTime)
             .Do(CheckForExitByTimeTargets)
             .Apply(this);

           Security
                   .WhenChanged()
                   .Do(CheckForExitByPriceTargets)
                   .Apply(this);

           SubscriptionEngine.Instance.RegisterSecurity(this, Security);

           if(((HustleEveryDayStrategyProperties)Params).OrderDirection==OrderDirections.Buy)
           {
               PlaceBuyMarketOrder(Security,Volume);

           }
           else
           {
               PlaceSellMarketOrder(Security, Volume);
           }

       }

       private  void CheckForExitByPriceTargets()
       {

         this.AddInfoLog("Проверка выхода по целям.");
       
         if( PositionManager.Positions.Any(p=> p.Security.Code==Security.Code))
         {
             var position = PositionManager.Positions.First(p => p.Security.Code == Security.Code);
             if (position.CurrentValue > 0)
             {
                 if (Security.LastTrade.Price <= ((HustleEveryDayStrategyProperties)Params).Stoploss)
                 { 
                    PlaceSellMarketOrder(position.Security, position.CurrentValue);
                    return;
                 }

                 if (Security.LastTrade.Price >= ((HustleEveryDayStrategyProperties)Params).TakeProfit3)
                 {
                     PlaceSellMarketOrder(position.Security, position.CurrentValue);
                     return;
                 }

                 if (Security.LastTrade.Price >= ((HustleEveryDayStrategyProperties)Params).TakeProfit2)
                 {
                     PlaceSellMarketOrder(position.Security,decimal.Floor(0.5m*position.CurrentValue));
                     ((HustleEveryDayStrategyProperties)Params).Stoploss =
                         ((HustleEveryDayStrategyProperties)Params).TakeProfit1;

                     return;
                 }
                 if (Security.LastTrade.Price >= ((HustleEveryDayStrategyProperties)Params).TakeProfit1)
                 {
                   
                     PlaceSellMarketOrder(position.Security, decimal.Floor(0.33m * position.CurrentValue));
                   
                     if(MyTrades.Any(t=>t.Trade.Security.Code==Security.Code))
                     {
                        var myTradesOrderedByDate = MyTrades.ToList().OrderBy(t => t.Trade.Time);
                        var trade = myTradesOrderedByDate.Last(t => t.Trade.Security.Code == Security.Code);
                        var entryPrice = trade.Trade.Price;

                         ((HustleEveryDayStrategyProperties) Params).Stoploss = entryPrice;

                     }
                     return;
                 }




             }
             else
             {
                 if (position.CurrentValue < 0)
                 {
                     if (Security.LastTrade.Price >= ((HustleEveryDayStrategyProperties)Params).Stoploss)
                     {
                         PlaceBuyMarketOrder(position.Security, position.CurrentValue);
                         return;
                     }

                     if (Security.LastTrade.Price <= ((HustleEveryDayStrategyProperties)Params).TakeProfit3)
                     {
                         PlaceBuyMarketOrder(position.Security, position.CurrentValue);
                         return;
                     }

                     if (Security.LastTrade.Price <= ((HustleEveryDayStrategyProperties)Params).TakeProfit2)
                     {
                         PlaceBuyMarketOrder(position.Security, decimal.Floor(0.5m * position.CurrentValue));
                         ((HustleEveryDayStrategyProperties)Params).Stoploss =
                             ((HustleEveryDayStrategyProperties)Params).TakeProfit1;

                         return;
                     }
                     if (Security.LastTrade.Price <= ((HustleEveryDayStrategyProperties)Params).TakeProfit1)
                     {

                         PlaceBuyMarketOrder(position.Security, decimal.Floor(0.33m * position.CurrentValue));

                         if (MyTrades.Any(t => t.Trade.Security.Code == Security.Code))
                         {
                             var myTradesOrderedByDate = MyTrades.ToList().OrderBy(t => t.Trade.Time);
                             var trade = myTradesOrderedByDate.Last(t => t.Trade.Security.Code == Security.Code);
                             var entryPrice = trade.Trade.Price;

                             ((HustleEveryDayStrategyProperties)Params).Stoploss = entryPrice;

                         }
                         return;
                     }




                 }
             }
          
              
         }
          

       }


       private  void CheckForExitByTimeTargets()
       {

           this.AddInfoLog("Выход по тайм-стопу");
       
             foreach (var position in PositionManager.Positions)
             {
                 if(position.CurrentValue>0)
                 {
                     PlaceSellMarketOrder(position.Security,position.CurrentValue);

                 }
           
                 if (position.CurrentValue < 0)
                 {
                     PlaceBuyMarketOrder(position.Security, -position.CurrentValue);


                 }
                  

             }

       }

       protected void PlaceBuyMarketOrder(Security security, decimal volume)
       {
           var order = this.BuyAtMarket(volume);
           order.Security = security;

           order
                          .WhenChanged()
                          .Do(() => this.AddInfoLog("Изменилось остояние заявки"))
                          .Once()
                          .Apply(this);



           order
               .WhenRegistered()
               .Do(() => this.AddInfoLog("Заявка успешно зарегистрирована"))
               .Once()
               .Apply(this);

           order
               .WhenRegisterFailed()
               .Do(() => this.AddInfoLog("Заявка не принята биржей"))
               .Once()
               .Apply(this);

           order
               .WhenMatched()
               .Do(() =>
               {

                   this.AddInfoLog("Заявка полностью исполнена");
               })
               .Once()
               .Apply(this);

           // регистрирация заявки

           RegisterOrder(order);

       }
       protected void PlaceSellMarketOrder(Security security, decimal volume)
       {
           var order = this.SellAtMarket(volume);
           order.Security = security;
           order
                          .WhenChanged()
                          .Do(() => this.AddInfoLog("Изменилось остояние заявки"))
                          .Once()
                          .Apply(this);



           order
               .WhenRegistered()
               .Do(() => this.AddInfoLog("Заявка успешно зарегистрирована"))
               .Once()
               .Apply(this);

           order
               .WhenRegisterFailed()
               .Do(() => this.AddInfoLog("Заявка не принята биржей"))
               .Once()
               .Apply(this);

           order
               .WhenMatched()
               .Do(() =>
               {

                   this.AddInfoLog("Заявка полностью исполнена");
               })
               .Once()
               .Apply(this);

           // регистрирация заявки

           RegisterOrder(order);

       }
   

    }
}
Topic starter
Thanks: Churchill liftrade dimtrdr

liftrade

Avatar
Training
Date: 12/3/2013
Reply


Интересно, это только у меня в каркасе в таблице сделки, отображается только первая сделка? Никакие последующие в нее не заносятся... Или это общий баг такой?
Thanks:

Kazai Mazai

Avatar
Articles author
Date: 12/12/2013
Reply


liftrade Перейти
Интересно, это только у меня в каркасе в таблице сделки, отображается только первая сделка? Никакие последующие в нее не заносятся... Или это общий баг такой?



да у меня тоже был такой баг, почему-то insert не так работает, как хотелось бы.

В StrategyDocument.xaml.cs меняем закомментированное на незакомментированное.

Код

_strategy.NewMyTrades += trades =>
				{
					foreach (var trade in trades)
					{
                                                _tradeGrid.Trades.Add(trade.Trade);
						//_tradeGrid.Trades.Insert(0, trade.Trade);
					}
				};



Topic starter
Thanks: liftrade

Aton5

Avatar
Training Donator
Date: 1/15/2014
Reply


где взять исходники S#.Shell, тем у кого лицензия это позволяет?
Дайте, пожалуйста, ссылку.
Thanks:

dij1

Avatar
Training
Date: 1/18/2014
Reply


скачал версию 4.2.2, там вообще Shell не открывается, библиотек основных нет.
Thanks:

Лебедев Сергей

Avatar
Training
Date: 2/12/2014
Reply


Месяц прошел - и молчание разработчиков!))
На самом деле Shell действительно достаточно сырой продукт, в который необходимо много вложить чтобы получить выхлоп.
Даже в версии от Казакова Сергея достаточно ошибок и неточностей, на исправление которых у меня ушло множество времени.

Так почти месяц назад я поставил для себя цель провести миграцию своих торговых роботов с платформы TradeMatic на платформу S#.Shell, и за это время наработал материал, которым стоит поделиться дабы не возникали подобные вопросы.
Thanks:

Лебедев Сергей

Avatar
Training
Date: 2/12/2014
Reply


Часть 4. Универсализация создания, тестирования и исполнения стратегий

Первое что мне бросилось в глаза в версии от Kazai Mazai, которую я продолжил допиливать - отсутствие универсальности в исполнении/тестировании одной и той же стратегии.
Так если запустить стратегию RobotStrategy на тестирование, то следующий участок кода стратегии:
Код

... lock (this)
	{
	  if (_bidOrder == null && _askOrder == null)
		{
		// проверяем на сигнал на вход
	       var spread = (ask - bid) / Security.MinStepSize;

		if (spread >= ((RobotStrategyProperties)Params).Spread)
...

вызовет ошибку "Не удалось привести тип объекта "System.Collections.Generic.List`1[Robot.Strategies.HustleEveryDayStrategyTestingProperties]" к типу "System.Collections.Generic.List`1[Robot.Strategies.HustleEveryDayStrategyProperties]".

Для решения этой проблемы введем в класс BaseShellStrategyProperties два параметра "Класс стратегии" и "Режим запуска":
Код


        public enum enMode
        {
           Execution,
           Testing
        }

        private string _classname = "Класс стратегии";
        [DisplayName(@"Класс")]
        [Description(@"Класс стратегии")]
        [Category(@"Основные")]
        [PropertyOrder(1)]
        public string ClassName
        {
            get { return _classname; }
            set
            {
                _classname = value;
                OnPropertyChanged("ClassName");
            }
        }


        private enMode _mode = enMode.Testing;
        [Category(@"Основные")]
        [DisplayName(@"Режим запуска")]
        [Description(@"Режим запуска стратегии")]
        [PropertyOrder(2)]
        public enMode Mode
        {
            get { return _mode; }
            set
            {
                _mode = value;
                OnPropertyChanged("Mode");
            }
        }


Данные свойства необходимо определять во всех процедурах типа AddХХХХStrategy.
Код

     private void AddRobotStrategy()
        {
            var properties = new RobotStrategyProperties
            {
                ClassName = "Robot",
                Mode = enMode.Execution



4.1. Универсализация исполнения стратегий
Все параметры внутри стратегии обертываем процедурой if, которая проверяет режим запуска и проводит правильное приведение типов данных:
Код

            int StopVal = 1;
            if (Params.Mode == enMode.Execution)
                StopVal = ((RobotStrategyProperties)Params).Stop;
            else
                StopVal = ((RobotTestingProperties)Params).Stop;



4.2. Универсализация тестирования стратегий
В основную процедуру запуска стратегий на тестирование StartTesingStrategy добавляем проверка класса стратегии при задании специфичных параметров:
Код

            if (parameters.ClassName == "Sma" || parameters.ClassName == "TLS")
            {
                switch (parameters.ClassName)
                {
                    case  "Sma":
                        ((SmaStrategy)_strategy).series = _series;
                        break;
                    case "TLS":
                        ((TLSStrategy)_strategy).series = _series;
                        break;
                }
            }



4.3. Универсализация сохранения настроек стратегий
В основной процедуре сохранения настроек стратегии SaveStrategies проводим модификацию запроса Where
Код

var robotstrategies = _documents.Keys.Where(str => str.Params.ClassName == "Robot" && str.Params.Mode == enMode.Execution).Select(str => (RobotStrategyProperties)str.Params).ToList();
SettingsEngine.Instance.SaveRobotStrategies(robotstrategies);

var hustlestrategies = _documents.Keys.Where(str => str.Params.ClassName == "Hustle" && str.Params.Mode == enMode.Execution).Select(str => (HustleStrategyProperties)str.Params).ToList();
SettingsEngine.Instance.SaveHustleStrProp(hustlestrategies);



4.4. Устранение бага отображения стратегий на панели "Стратегии"
В поставляемой версии на панели "Стратегии" не отражаются тестовые стратегии. Для устранения этого бага проводим универсализацию хранения стратегий в переменной _documents.
а) везде в процедурах создания стратегий проводим сохранение стратегий в переменную _documents:
Код

                _documents.Add(strategy, doc); 

б) в свойстве SelectedStrategy дописываем условие выбора документа типа TestingDocument:
Код

                var doc = dockManager.ActiveContent;

                if (doc is StrategyDocument)
                {
                    var content = (StrategyDocument)doc;
                    result = content.Strategy;
                }
                else
                {
                    var content = (TestingDocument)doc;
                    result = content.Strategy;
                }
                return result;

Теперь все отображается корректно.
в) дополнительно можно вообще удалить процедуру SaveTestingStrategies, поместив соответствующие обработки в SaveStrategies:
Код

var robotTstrategies = _documents.Keys.Where(str => str.Params.ClassName == "Robot" && str.Params.Mode == enMode.Testing).Select(str => (RobotStrategyTestingProperties)str.Params).ToList();
SettingsEngine.Instance.SaveRobotTestingStrategies(robotTstrategies);

var hustleTstrategies = _documents.Keys.Where(str => str.Params.ClassName == "Hustle" && str.Params.Mode == enMode.Testing).Select(str => (HustleStrategyTestingProperties)str.Params).ToList();
SettingsEngine.Instance.SaveHustleTestStrProp(hustleTstrategies);



После всех этих универсализаций добавление новой стратегии действительно становится достаточно простым делом.
StrategyPane.jpg 253 KB (0)
Thanks:

Лебедев Сергей

Avatar
Training
Date: 2/16/2014
Reply


Часть 5. Чему нас учит семья и школа

Интерфейс тестирования S#.Shell по умолчанию не достаточен для эффективного тестирования стратегии при ее первичном кодировании, так как в этом тонком процессе переноса собственного нейронного вещества в машинный код возникает множество нюансов а-ля "сделка не выполнилась в силу неизвестных причин", в которых невозможно разобраться без соответствующих инструментов.

Поэтому добавляем в наш S.Shell основные инструменты визуализации процесса исполнения стратегии, которые нам уже знакомы по курсу S#.Education - исторический график свечей, таблицу заявок и таблицу собственных сделок.

5.1. Создаем на TestingPanel элемент типа TabControl, в который добавляем вкладки для каждого инструмента:
Код

                <TabItem x:Name="TabHist" Header="History">
                    <chart:Chart x:Name="HistoryChart" MinHeight="200" Height="Auto" x:FieldModifier="public"/>
                </TabItem>
                <TabItem x:Name="TabPNL" Header="PNL">
                    <chart:EquityCurveChart x:Name="CurveChart" MinHeight="200" Height="Auto"  x:FieldModifier="public"/>
                </TabItem>
                <TabItem x:Name="TabORD" Header="Orders">
                    <xaml:OrderGrid x:Name="myOrders" MinHeight="200" Height="Auto" x:FieldModifier="public"/>
                </TabItem>
                <TabItem x:Name="TabTRD" Header="Trades">
                    <xaml:MyTradeGrid x:Name="myTrades" MinHeight="200" Height="Auto" x:FieldModifier="public"/>
                </TabItem>


5.2. В общем-то мы понимаем, что графические элементы в целом тормозят тестирование и нужны будут не всегда, а в основном при кодинге новых стратегий, поэтому в свойства базовой стратегии тестирования BaseShellStrategy добваляем свойства для управления отображением их в ходе тестирования:
Код

        private bool _chartPL = false;
        [Category(@"Отрисовка")]
        [DisplayName(@"График P&L")]
        [Description(@"Вывод графика P&L при тестировании")]
        [PropertyOrder(0)]
        public bool chartPL
        {
            get { return _chartPL; }
            set
            {
                _chartPL = value;
                OnPropertyChanged("chartPL");
            }
        }

        private bool _chartHC = false;
        [Category(@"Отрисовка")]
        [DisplayName(@"График свечей")]
        [Description(@"Вывод графика свечей  при тестировании")]
        [PropertyOrder(2)]
        public bool chartHC
        {
            get { return _chartHC; }
            set
            {
                _chartHC = value;
                OnPropertyChanged("chartHC");
            }
        }

        private bool _chartTrd = false;
        [Category(@"Отрисовка")]
        [DisplayName(@"Сделки")]
        [Description(@"Вывод всех сделок на график истории")]
        [PropertyOrder(2)]
        public bool chartTrd
        {
            get { return _chartTrd; }
            set
            {
                _chartTrd = value;
                OnPropertyChanged("chartTrd");
            }
        }



5.3. В процедуре тестирования стратегии StartTesingStrategy прописываем код для обоработки каждого нового инструмента:
а) для истории свечей:
Код

            if (parameters.chartHC)
            {
                    histChart = doc.TestingPanel.HistoryChart;
                    histChart.Areas.Clear();
                    histChart.IsAutoScroll = true;
                    _area = new ChartArea(); //создаем область на графике
                    histChart.Areas.Add(_area); //добавляем область на график
                    _candlesElem = new ChartCandleElement(); //создаем элемент свечи
                    _area.Elements.Add(_candlesElem); //добавляем элемент в область вывода графика
                    _candleManager.Processing += (series, cnd) =>
                    {
                        if (cnd.State == CandleStates.Finished)
                        {
                            var myDic = new Dictionary<IChartElement, object>();
                            myDic.Add(_candlesElem, cnd);
                            MainWindow.Instance.GuiAsync(() => histChart.ProcessValues(cnd.OpenTime, myDic));
                        }
                    };
            }

б) для отражения истории сделок:
Код

            if (parameters.chartHC && parameters.chartTrd)
            {
                    var _tradeElement = new ChartTradeElement();
                    _area.Elements.Add(_tradeElement);
                    _strategy.NewMyTrades += (trds) => 
                        trds.ForEach(t =>
                        {
                            var tradeTime = _timeFrame.GetCandleBounds(t.Trade.Time).Min;
                            var myDic = new Dictionary<IChartElement, object>() { { _tradeElement, t } };
                            MainWindow.Instance.GuiAsync(() => histChart.ProcessValues(tradeTime, myDic));
                        });
            }

в) для таблицы заявок:
Код

            var _myOrders = doc.TestingPanel.myOrders;
            _myOrders.Orders.Clear();
            _strategy.OrderRegistered += (ord) => MainWindow.Instance.GuiAsync(() => _myOrders.Orders.Add(ord));

г) для таблицы сделок:
Код

            var _myTrades = doc.TestingPanel.myTrades;
            _myTrades.Trades.Clear();
            _strategy.NewMyTrades += (trd) => trd.ForEach(t => MainWindow.Instance.GuiAsync(() => _myTrades.Trades.Add(t)));


И в результате все работет отлично, кроме корректного отображения сделок))
History.jpg 529 KB (0) Orders.jpg 710 KB (1) MyTrades.jpg 430 KB (0)
Thanks:

kesot

Avatar
Date: 2/20/2014
Reply


А почему для установки стопов не пользуетесь StopLossStrategy, а в ручную их отслеживаете?
Thanks:

Лебедев Сергей

Avatar
Training
Date: 2/24/2014
Reply


на мой взгляд использование дочерних стратегий тут дело вкуса - кому-то идет, а кому-то нет; в силу разных обстоятельств - и дебаггить их невозможно, условия срабатывания ограничены (н--р нет стоп-лосса исходя из % годовых) и пр.
Thanks:

Лебедев Сергей

Avatar
Training
Date: 3/1/2014
Reply


Часть 6 - Тотальный апгрейд

Итак имеем S#.Shell на основе S#.API 4.1.15.0 и решаемся на апгрейд до версии 4.2.2.16.
Сразу же получаем множество заменой iTrader на Connector, и еще с десяток проблемных мелочей типа замены свойств, которые решаются в течение получаса

Но так же вылезают две более серьезные проблемы, о которых имеет смысл тут рассказать.

1. В XAML отваливается весь CommandBinding с тупым сообщением "Член не распознан"))
Как единственное найденное 100% работающее решение данной проблемы - перенос всего CommandBindingа из XAML в программный код.
Для этого делаем три простых шага:
1) в MainWindow.xaml полностью удаляем содержание раздела <Window.CommandBindings> и привязки команд типа Command="{x:Static Robot:MainWindow.ConnectCommand}", а вместо этого для каждого элемента, которому необходимо привязать RoutedCommand задаем имя через тег x:Name
2) в MainWindow.xaml.cs создаем новую процедуру private void InitializeCommands(), в которой для каждого управляющего элемента создаем связь через класс CommandBinding (на примере пункта меню "Подключиться"):
Код

            CommandBinding ccBinding1 = new CommandBinding(ConnectCommand, ExecutedConnect, CanExecuteConnect);
            this.CommandBindings.Add(ccBinding1);
            miConnect.Command = ConnectCommand;

где ConnectCommand - исходная RoutedCommand,
ExecutedConnect - основная процедура для выполнения,
CanExecuteConnect - связанная проверочная процедура возможности выполнения основной процедуры,
miConnect - название элемента пункта меню "Подключиться",
далее в тоже процедуре InitializeCommands() создаем необходимые элементы для быстрых клавиш:
Код

            KeyGesture OpenKeyGesture1 = new KeyGesture(Key.P,ModifierKeys.Control);
            InputBinding KeyBinding1 = new KeyBinding(SettingsCommand, OpenKeyGesture1);
            this.InputBindings.Add(KeyBinding1);

3) вызываем InitializeCommands() в теле процедуры MainWindow().

2. В S#.API версии 4.2.2.16 обнаруживаем, что в режиме эмуляции ни один из коннекторов (проверено на AlfaTrader/TransaqTrader) не получает портфели. Соответственно тестирование в эмуляции невозможно.
Для решения этой проблемы делаем два шага:
1) создаем в классе ConnectionEngine свойство IsEmulation:
Код

                   public bool IsEmulation { get { return settings.Emulation; } }

2)добавляем в код процедуры StartStrategy специальную проверку на режим эмуляции и в случае оного, создаем тестовый портфель, который и привязываем к коннектору:
Код
 
		    var portfolio = ConnectionEngine.Instance.Trader.Portfolios.FirstOrDefault(p => p.Name == strategy.Params.Portfolio);
		    if (portfolio == null)
		    {
		        if (ConnectionEngine.Instance.IsEmulation)
		        {
                    var portf = new Portfolio{Name = "TestAcc",BeginValue = 1000000,CurrentValue = 1000000};
                    ConnectionEngine.Instance.Trader.RegisterPortfolio(portf);
                    ConnectionEngine.Instance.Trader.TransactionAdapter.SendInMessage(portf.ToMessage());
                    var pcm =new PortfolioChangeMessage{PortfolioName = portf.Name}.Add(PositionChangeTypes.BeginValue, portf.BeginValue);
		            ConnectionEngine.Instance.Trader.TransactionAdapter.SendInMessage(pcm);
                    var pcm2 = new PortfolioChangeMessage { PortfolioName = portf.Name }.Add(PositionChangeTypes.CurrentValue, portf.CurrentValue);
                    ConnectionEngine.Instance.Trader.TransactionAdapter.SendInMessage(pcm2);
		            strategy.Portfolio = portf;
                    strategy.AddWarningLog("Создан портфель {0} для запуска стратегии", portf.Name);
		        }
		        else
		        {
                    strategy.AddErrorLog("Не найден портфель {0} для запуска стратегии", strategy.Params.Portfolio);
                    return;    
		        }
		        
		    }
		    else
		        strategy.Portfolio = portfolio;


Вуаля- все работает как нужно!
Thanks: methyst

methyst

Avatar
Training
Date: 3/10/2014
Reply


lebedevsrg, собираетесь ли Вы выкладывать свою версию shell'a куда-нибудь?
Thanks:

Лебедев Сергей

Avatar
Training
Date: 3/10/2014
Reply


methyst, тут такие правила что по окончанию срока обучения (1,5 или 3 мес ) пользователей отключают от TFS.
По вопросу - полагаю что смысла каждому пользователю выкладывать свои программы на TFS особого нет.
Материалы я тут опубликовал для общего развития тех кто захочет сам допиливать Shell.
Thanks:

Mikhail Sukhov

Avatar
Articles author Programmer Trader
Date: 3/11/2014
Reply


lebedevsrg Перейти
methyst, тут такие правила что по окончанию срока обучения (1,5 или 3 мес ) пользователей отключают от TFS.


Таких правил нет. https://stocksharp.ru/posts/m/29944/
Thanks:

Лебедев Сергей

Avatar
Training
Date: 4/14/2014
Reply


Часть 7 - Чем StockSharp круче Tradematic, или о миграции стратегий

Итак рассмотрим вкратце миграцию стратегий на примере простейшей стратегии торговли по линиям капитала, которая подробно изложена тут (http://robotcraft.ru/vstroeniestrategii/strateg-akc). Заодно пополним коллекцию торговых стратегий StockSharp достаточно надежной и низкорискованной стратегий, хотя с небольшой годовой доходностью (9% -15%).

В целом система Tradematic так же позволяет программировать стратегии на с#, но без доступа к исходному коду оболочки, т.е. это что-то типа S#.Studio.
При этом в ней есть ряд преимуществ по сравнению со S#.Studio:
1) имеется встроенный оптимизатор стратегий,
2) лицензия включает доступ к Серверу с историческими данными, позволяющему проводить облачное тестирование
(как известно доступ к такому серверу StockSharp идет за отдельную плату),
3) имеется "торговая книга", которая автоматически матчит открытие и закрытие позиций.
Но все эти преимущества меркнут по сравнению с теми колоссальными возможностями, которые нам дает открытая библиотека StockSharp, и которые будут проиллюстрированы ниже.


Итак для реализации нашей стратегии создаем общий класс TradeManager, назначением которого будет:
1) создание и ведение Плана торговли(Plan),
2) ведение Торговой книги(Book).
Данный класс сразу делаем общий, так как он в дальнейшем может быть использован в пирамидальных стратегиях любой сложности.

1) Создаем класс TradeManager и его инициализатор:
Код
 
    public class TradeManager : ILogReceiver
    {
        private BaseShellStrategy _stratergy { get; set; }
        public Dictionary<int, PlanElem> Plan;
        public SynchronizedCollection<BookElem> Book;
        
        public TradeManager(BaseShellStrategy _str)
        {
            if (_stratergy==null) _stratergy = _str;
            if (Plan == null) Plan = new Dictionary<int, PlanElem>();
            if (Book == null) Book = new SynchronizedCollection<BookElem>();
        }        
     ...
    }


2) Для создания Плана торговли объявляем дополнительный класс PlanElem, каждый экземпляр которого будет содержать информацию об одной торговой линии:
Код

    public class PlanElem
    {
        public int Id { get; set; }
        public decimal PlanPrice { get; set; }
        public string SignalName { get; set; }
        public int Vol { get; set; }
        public decimal Amount { get; set; }
        public decimal SellPrice { get; set; }
        public decimal RiskShort { get; set; }
        public decimal RiskLong { get; set; }
        public DateTime BuyDate { get; set; }
        public DateTime SellDate { get; set; }
        public long OrderId { get; set; }
        public long TradeId { get; set; }
    }


Так же создаем класс для быстрой параметризации Плана торговли при его первичном создании:
Код

    // Параметры плана торговли
    public class PlanParams
    {
        public int MaxPriceVal { get; set; }
        public int MinPriceVal { get; set; }
        public int StepVal { get; set; }
        public decimal riskShortVal { get; set; }
        public decimal riskLongVal { get; set; }
        public decimal MarginVal { get; set; }
    }


Далее создаем метод CreateTradePlan для создания План торговли по указанным параметрам:
Код

public void CreateTradePlan(PlanParams prm)
        {
            
            if (prm.MaxPriceVal <= prm.MinPriceVal || prm.MaxPriceVal == 0 || prm.MinPriceVal == 0 || prm.StepVal == 0 ||
                prm.StepVal >= prm.MinPriceVal)
                return;

            _NBins = 3 + (prm.MaxPriceVal - prm.MinPriceVal) / prm.StepVal;
            if (_NBins > prm.Maxlength)  // Ограничение на длину плана торгов
            {   
                this.AddErrorLog("Вычисленная длина плана превышает допустимую");
                return; 
            }

            decimal curLevel = 0;
            ShortCapVal = 0;
            LongCapVal = 0;

            for (int i = 0; i <= _NBins; i++)
            {
                
                planElem = new PlanElem();
                planElem.Id = i + 1;

                if (i == 0)
                {   planElem.PlanPrice = 0;
                    planElem.SellPrice = prm.MinPriceVal;
                }
                else if (i == 1)
                {   curLevel = prm.MinPriceVal;
                    planElem.SellPrice = curLevel + prm.StepVal;
                }
                else if (i == _NBins)
                {   curLevel = prm.MaxPriceVal;
                    planElem.SellPrice = prm.MaxPriceVal * 1000 + prm.StepVal;
                }
                else
                {   curLevel = curLevel + prm.StepVal;
                    planElem.SellPrice = curLevel + prm.StepVal;
                }

                planElem.PlanPrice = curLevel;
                planElem.Amount = _stratergy.Volume;
                if (prm.iType == SecurityTypes.Stock)
                {
                    planElem.CapShort = curLevel * _stratergy.Volume;
                    planElem.CapLong = curLevel * _stratergy.Volume;
                }
                else
                {
                    planElem.CapShort = (curLevel * prm.CapShortVal + prm.MarginVal) * _stratergy.Volume;
                    planElem.CapLong = (curLevel * prm.CapLongVal + prm.MarginVal) * _stratergy.Volume;
                }
                ShortCapVal = ShortCapVal + planElem.CapShort;
                LongCapVal = LongCapVal + planElem.CapLong;
                planElem.SignalName = "Buy at " + curLevel.ToString();
                planElem.Vol = 0;
                Plan.Add(i, planElem);
            }

            Book.Clear();
            this.AddInfoLog("Создан План торговли из {0} уровней ".Put(_NBins));
        }



3) Создаем процедуры для ведения Торговой книги:
а) в первую очередь делаем процедуру первичной регистрации RegisterPosition, которая будет срабатывать сразу полсе появления заявки:
Код

        public void RegisterPosition(Order ord)
        {
            if (ord.Direction == OrderDirections.Sell) return; // отсев заявок на продажу
            if (ord.Comment.Length > 3) return; // отсев заявок с перерегситрацией

            int curi = Book.Count();
            var inum = (int)ord.Comment.To<int>(); // порядковый номер плана
            var pl = Plan.TryGetValue(inum - 1);
            var BookElem = new BookElem();
            BookElem.Id = curi;
            BookElem.PlanPrice = pl.PlanPrice;
            BookElem.SignalId = pl.Id;
            BookElem.Amount = (int)_stratergy.Volume;
            BookElem.BuyOrder = ord.TransactionId;
            BookElem.BuyOrderDate = ord.LastChangeTime;
            Book.Add(BookElem);
            ord.Comment = "Уровень {0} зарегистрирован".Put(inum);
            this.AddInfoLog("Уровень {0} зарегистрирован", ord.TransactionId);
        }

б) затем создаем процедуру UpdateBook, которая призвана записывать в Торговую книгу данные о связанных сделках покупки и продажи:
в зависимости от направления проводим связывание элементов Заявка-Сделка:
- сделки на покупку сопоставляются с заявками по TransactionId, который предварительно сохраняется в поле BuyOrder при регистрации,
- сделки на продажу сопоставляются со сделками на покупки по TransactionId, записанным в поле Comment заявки на продажу.
Код

        public void UpdateBook(IEnumerable<MyTrade> trades)
        {
            foreach (var myTrade in trades)
            {
                var ord = myTrade.Order;
                int ordnum = -1;

                if (ord.Direction == OrderDirections.Buy)
                { // дополняем таблицу данными о сделке покупки

                    string tID = ord.TransactionId.ToString();
                    if (tID.Length > 8)
                    { // вырезаем основную часть из заявок с переносом
                        tID = tID.Substring(0, 8);
                    }

                    var ordlast = Book.Where(f => f.BuyOrder.ToString() == tID);
                    if (ordlast.Any() == false)
                    {
                        for (int i = 0; i < Book.Count; i++)
                            if (Book[i].f.BuyOrder.ToString() == tID) ordnum = i + 1;
                    }
                    else ordnum = ordlast.First().Id; // Key связанной Позиции

                    planElem = Plan[Book[ordnum].SignalId-1];
                    planElem.TradeId = myTrade.Trade.Id; // регистрируем выполнение заявки
                    
                    Book[ordnum].BuyPrice = myTrade.Trade.Price;
                    Book[ordnum].BuyTrade = myTrade.Trade.Id;
                    Book[ordnum].BuyTradeDate = myTrade.Trade.Time;

                }
                else
                { // дополняем таблицу данными о сделке продажи

                    var ordlast = Book.Where(f => f.BuyOrder.ToString() == ord.Comment);
                    //ordlast = Book.Where(f => String.Compare(f.BuyOrder.ToString(),ord.Comment)==0);
                    if (ordlast.Any() == false)
                    {
                        for (int i = 0; i < Book.Count; i++)
                            if (Book[i].ToString() == ord.Comment) ordnum = i + 1;
                    }
                    else ordnum = ordlast.First().Id;
                    
                    planElem = Plan[Book[ordnum].SignalId - 1];
                    planElem.TradeId = 0; // регистрируем выполнение заявки
                    planElem.Vol = 0; // снимаем размер 

                    Book[ordnum].SellOrder = ord.TransactionId;
                    Book[ordnum].SellOrderDate = ord.Time;
                    Book[ordnum].SellPrice = myTrade.Trade.Price;
                    Book[ordnum].SellTrade = myTrade.Trade.Id;
                    Book[ordnum].SellTradeDate = myTrade.Trade.Time;
                    Book[ordnum].prFit = Book[ordnum].SellPrice - Book[ordnum].BuyPrice;
                    Book[ordnum].prPcn = Book[ordnum].prFit / Book[ordnum].BuyPrice;

                    var span = (Book[ordnum].SellTradeDate - Book[ordnum].BuyTradeDate);
                    decimal spanVal = 0; 
                    if   (span.TotalDays < 1) spanVal = 1;
                    else spanVal = (decimal) span.TotalDays;
                    Book[ordnum].prAnn = Book[ordnum].prPcn * 365 / spanVal;

                 }

            }
        }
Thanks: Иван З. nik

Лебедев Сергей

Avatar
Training
Date: 4/14/2014
Reply


4) далее создаем торговую стратегию TLSStrategy, в которой основными свойствами будут
Код

---
        private int _maxPrice = 110;
        [DisplayName(@"Max Price")]
        [Description(@"Минимальная цена")]
---
        private int _minPrice = 80;
        [DisplayName(@"Min Price")]
        [Description(@"Максимальная цена")]
---
        private int _step = 1;
        [DisplayName(@"Step")]
        [Description(@"Шаг цены")]
---
        private decimal _LongCapVal = 0;
        [Browsable(false)]
        [DisplayName(@"LongCapVal")]
        [Description(@"Оценка капитала")]
---
        private decimal _NBins = 0;
        [Browsable(false)]
        [DisplayName(@"NBins")]
        [Description(@"Количество уровней")]

В самой стратегии реализуем несколько открытых свойств для связи с интерфейсом:
Код

        public int NBins { get; set; }
        public CandleSeries series { get; set; }
        public SynchronizedCollection<PlanElem> StrPlan { get; set; }
        public SynchronizedCollection<BookElem> StrBook { get; set; }
        public TradeManager trman;

И далее создаем три основных процедуры, которые будут обеспечивать ее работу:
а) процедуру инициации, основное предназначение которой - создать План торгов до старта стратегии
Код

 public void Initialize()
        {
            this.Volume = Params.Volume;
            if (StrPlan == null || StrPlan.Count == 0) StrPlan = new SynchronizedCollection<PlanElem>();

            prmStr = new PlanParams();
            prmStr.MaxPriceVal = Params.Mode == enMode.Execution ? ((TLSStrProp)Params).MaxPrice : ((TLSTestProp)Params).MaxPrice;
            prmStr.CurPriceVal = Params.Mode == enMode.Execution ? ((TLSStrProp)Params).СurPrice : ((TLSTestProp)Params).СurPrice;
            prmStr.MinPriceVal = Params.Mode == enMode.Execution ? ((TLSStrProp)Params).MinPrice : ((TLSTestProp)Params).MinPrice;
            prmStr.StepVal = Params.Mode == enMode.Execution ? ((TLSStrProp)Params).Step : ((TLSTestProp)Params).Step;
            prmStr.CapShortVal = Params.Mode == enMode.Execution ? ((TLSStrProp)Params).CapShort : ((TLSTestProp)Params).CapShort;
            prmStr.CapLongVal = Params.Mode == enMode.Execution ? ((TLSStrProp)Params).CapLong : ((TLSTestProp)Params).CapLong;
            prmStr.MarginVal = Params.Mode == enMode.Execution ? Security.MarginBuy : ((BaseShellTestProp)Params).Margin;
            prmStr.Maxlength = 500;
            prmStr.iType = Security.ExchangeBoard == ExchangeBoard.Forts ? SecurityTypes.Future : SecurityTypes.Stock;

            trman = new TradeManager(this);
            trman.CreateTradePlan(prmStr);
            StrBook = trman.Book;
        }

б) процедуру запуска, в которой устанавливаем основные вызовы процедур обработки свечей и ведения Торговой книги
Код

        protected override void OnStarted()
        {
            base.OnStarted();
            if (StrPlan == null || StrPlan.Count == 0) Initialize();

            series
                .WhenCandlesFinished()
                .Do(ProcessCandle)
                .Apply(this);

            this.WhenOrderRegistered()
                .Do(trman.RegisterPosition) // Создание записи в журнале о заявке
                .Apply();

            this.WhenNewMyTrades()
                .Do(trman.UpdateBook) // Обновление позиции после выполнения заявки в торговой системе
                .Apply();
            
            this.OrdersKeepTime = TimeSpan.FromDays(30);
        }

в) и конечно же процедуру обработки свечей, в которой реализуем основную логику создания заявок на покупку/продажу актива:
Код

private void ProcessCandle(Candle candle)
{
			
            var timeFrame = (TimeSpan)candle.Arg;
            var time = timeFrame.GetCandleBounds(Security).Min - timeFrame;
		    if (candle.OpenTime < time)
		        return;

		    _CandeslProcessed = _CandeslProcessed + 1;

            // если наша стратегия в процессе остановки
			if (ProcessState == ProcessStates.Stopping)
			{// отменяем активные заявки
				CancelActiveOrders();
				return;
			}

	   // обрабатываем новую свечку
            int mhit = 0;
	    decimal closepr = candle.ClosePrice;
            DateTime DT = candle.CloseTime;
            var planElemCur = new PlanElem();
            var planElemPrev = new PlanElem();

            for (int i = 1; i <= NBins; i++)
            {
                planElemCur = trman.Plan[i];
                planElemPrev = trman.Plan[i - 1];
                string s = "Hold";
                if (closepr > planElemPrev.PlanPrice && closepr <= planElemCur.PlanPrice)
                    mhit = i; // отмечаем уровень цены mhit как номер планового уровня i			

                if (mhit > 0 && planElemCur.Vol == 0 && planElemCur.TradeId == 0) // если цена ниже планового уровня, а уровень пуст то закупаем
                {   
                    var capAv = CheckCap();
                    if (capAv)
                    {//создаем ордер на открытие позиции
                        var order = this.CreateOrder(OrderDirections.Buy, closepr + 10 * Security.MinStepSize, Volume);
                        order.Comment = (i + 1).ToString(); // порядковый номер в плане i+1
                        planElemCur.Vol = (int)Volume;
                        planElemCur.BuyDate = DT;
                        RegisterOrder(order);
                        planElemCur.OrderId = order.TransactionId;
                    }
                    else
                    {  this.AddWarningLog("Trade is missed for capital breach!");
                    }                    
                }

                if (mhit == 0 && planElemCur.Vol > 0 && planElemPrev.TradeId > 0) // если на уровне ниже чем цена оказывается позиция j+1, то ее закрываем
                {
                    planElemPrev.SellDate = DT;
                    //создаем ордер на закрытие позиции                     
                    var orderCl = this.CreateOrder(OrderDirections.Sell, planElemPrev.SellPrice, planElemPrev.Vol);
                    orderCl.Comment = planElemPrev.OrderId.ToString(); // сохраняем первый номер для связки ордеров
                    RegisterOrder(orderCl);
                }
            }
    }


5) очевидно, что капитала может не хватить в процессе резкого падения рынка, как это было при присоединении Крыма, поэтому
а) создаем свойства стратегии для хранения размера использованного капитала и %
Код

         private decimal _longCapt = 0;
        [Browsable(false)]
        [DisplayName(@"longCapt")]
        [Description(@"Использованный капитал")]
---
        private decimal _longPcnt = 0;
        [Browsable(false)]
        [DisplayName(@"longPcnt")]
        [Description(@"Процент использованния капитала")]

б) создаем в классе TradeManager счетчик CalcCapReq для подсчета использованного капитала:
Код

        public void CalcCapReq()
        {
            int longCountVal = 0;
            decimal longCaptVal = 0;
            decimal longPcntVal = 0;

            if (Plan == null) return;

            for (int i = 0; i <= _NBins; i++)
            {
                if (Plan[i].Vol > 0)
                {
                    longCountVal = longCountVal + 1;
                    longCaptVal = longCaptVal + Plan[i].CapLong;
                }
            }

            if (LongCapVal > 0)
                longPcntVal = longCaptVal / LongCapVal;
            else longPcntVal = 0;
      }

в) вызываем данный счетчик после открытия длинной позиции, например в процедуре UpdateBook
с) при обработки сигнала на покупки проверяем наличие капитала в процедуре CheckCap
Код

        private bool CheckCap()
        {
            var lnCaP = (Params.Mode == enMode.Execution) ? ((TLSStrProp)Params).longPcnt : ((TLSTestProp)Params).longPcnt;
            if (lnCaP>(decimal)0.99) this.AddWarningLog("Capital limit is reached");
            return (lnCaP < 1);

        }

и если капитала недостаточно для удержания уже набранной позиции, то открытие новых позиций не проводим, создавая об этом запись в логе this.AddWarningLog("Trade is missed for capital breach!")
Thanks:

Лебедев Сергей

Avatar
Training
Date: 4/14/2014
Reply


Итак в целом миграция завершена. Нужно признать что кода в StockSharp получилось гораздо больше, чем Tradematic - раза в три больше.
Но тут как раз и пора показать основное преимущество StockSharp - открытость кода для создания пользователького интерфейса управления работающей стратегией.

6) Создаем элементы для визуализации кастомарных свойств новой стратегии
а) вначале создаем StrategyPlanDG для привязки Плана торговли
Код

<Grid DataContext="{Binding RelativeSource={RelativeSource Self}}" >
        <DataGrid Name="PlanDG" AutoGenerateColumns="False" IsReadOnly="False" >
                <DataGrid.Columns>
                <DataGridTextColumn Width="30"  Header="Id" Binding="{Binding Path=Id}" IsReadOnly="True" />
                <DataGridTextColumn Width="80"  Header="Сигнал" Binding="{Binding Path=SignalName}" IsReadOnly="True"/>
                <DataGridTextColumn Width="80" Header="Покупка" Binding="{Binding Path=PlanPrice, StringFormat=N0}" IsReadOnly="True"/>
                <DataGridTextColumn Width="80" Header="Продажа" Binding="{Binding Path=SellPrice, StringFormat=N0}" IsReadOnly="True" />
                <DataGridTextColumn Width="60"  Header="Объем" Binding="{Binding Path=Amount, ValidatesOnExceptions=True,  StringFormat=F0}" IsReadOnly="True" />
                <DataGridTextColumn Width="60" Header="Прибыль" Binding="{Binding Path=LevelGain, StringFormat=P2}" IsReadOnly="True" />
                <DataGridTextColumn Width="80" Header="CapShort" Binding="{Binding Path=CapShort,StringFormat=N0}" IsReadOnly="True" />
                <DataGridTextColumn Width="80" Header="CapShort%" Binding="{Binding Path=CapShortPcn,StringFormat=P2}" IsReadOnly="True" />
                <DataGridTextColumn Width="80" Header="CapLong" Binding="{Binding Path=CapLong,StringFormat=N0}" IsReadOnly="True" />
                <DataGridTextColumn Width="80" Header="CapLong%" Binding="{Binding Path=CapLongPcn,StringFormat=P2}" IsReadOnly="True" />
                <DataGridTextColumn Width="100" Header="Посл.покупка" Binding="{Binding Path=BuyDate, StringFormat='dd.MM.yy HH:mm'}" IsReadOnly="True"/>
                <DataGridTextColumn Width="60"  Header="Позиция" Binding="{Binding Path=Vol, Mode=TwoWay, StringFormat=N0}" />
                <DataGridTextColumn Width="100" Header="Посл.продажа" Binding="{Binding Path=SellDate, StringFormat='dd.MM.yy HH:mm'}" IsReadOnly="True"/>
            </DataGrid.Columns>

и выводим его на вкладку в элементе TestingPanel стандартного интерфейса S#.Shell +
Код
 
                <TabItem x:Name="TabPlan" Header="Plan" Visibility="Hidden">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="25"></RowDefinition>
                            <RowDefinition Height="25"></RowDefinition>
                            <RowDefinition Height="*"></RowDefinition>
                        </Grid.RowDefinitions>
                        <DockPanel>
                            <Button x:Name="btnCalc" Width="80" Content="Create" Click="BtnCalc_OnClick" Margin="10,5,0,0" HorizontalAlignment="Left" ></Button>
                            <Button x:Name="btnFill" Width="80" Content="Fill" Click="BtnFill_OnClick" Margin="10,5,0,0" HorizontalAlignment="Left" />
                            <Button x:Name="btnRecalc" Width="80" Content="Usage" Click="BtnRecalc_OnClick" Margin="10,5,0,0" HorizontalAlignment="Left" />
                        </DockPanel>
                        <DockPanel x:Name="CapitalInfo" Grid.Row="1" x:FieldModifier="public">
                            <Label Content="Уровней" Width="60" HorizontalAlignment="Left" />
                            <TextBlock x:Name="nbins" Width="40" Text="{Binding Path=NBins}" x:FieldModifier="public" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            <Label Content="Cap Short" Width="70" HorizontalAlignment="Left" />
                            <TextBlock x:Name="shortVal" Width="60" Text="{Binding Path=ShortCapVal, StringFormat=N0}" x:FieldModifier="public" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            <Label Content="Cap Long" Width="70" HorizontalAlignment="Left" />
                            <TextBlock x:Name="longVal" Width="60" x:FieldModifier="public" Text="{Binding Path=LongCapVal, StringFormat=N0}" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            <Label Content="Занято" Width="60" HorizontalAlignment="Left" Margin="10,0,0,0" />
                            <TextBlock x:Name="filVal" Width="40"  Text="{Binding Path=longCount}" x:FieldModifier="public" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            <Label Content="Исп. капитала" Width="100" HorizontalAlignment="Left" />
                            <TextBlock x:Name="utilVal" Width="60"  Text="{Binding Path=longCapt, StringFormat=N0}" x:FieldModifier="public" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            <Label Content="Исп.%" Width="50" HorizontalAlignment="Left" />
                            <TextBlock x:Name="utilPcn" Width="40" Text="{Binding Path=longPcnt, StringFormat=P2}" x:FieldModifier="public" HorizontalAlignment="Left" VerticalAlignment="Center" />
                        </DockPanel>
                        <robot:StrategyPlanDG x:Name="myPlanDG" x:FieldModifier="public" Grid.Row="2"/>
                    </Grid>
                </TabItem>

по умолчанию делаем элемент скрытым, чтобы не мешал при работе других стратегий.
б) создаем StrategyBook для визуализации торговой книги
Код

    <ListView x:Name="BookView"  x:FieldModifier="public" SelectionMode="Single"
              DataContext="{Binding RelativeSource={RelativeSource Self}}"
              HorizontalContentAlignment="Stretch">
        <ListView.View >
            <GridView >
                <GridViewColumn Width="30"  Header="Id" DisplayMemberBinding="{Binding Path=Id}" />
                    <GridViewColumn Width="80" Header="Уровень" DisplayMemberBinding="{Binding Path=PlanPrice, StringFormat=F2}"/>
                    <GridViewColumn Width="100" Header="Заявка покупки" DisplayMemberBinding="{Binding Path=BuyOrder}" />
                <GridViewColumn Width="80" Header="Дата заявки" DisplayMemberBinding="{Binding Path=BuyOrderDate, StringFormat='dd.MM.yy HH:mm'}" />
                    <GridViewColumn Width="100" Header="Сделка покупки" DisplayMemberBinding="{Binding Path=BuyTrade, StringFormat=F0}" />
                <GridViewColumn Width="80" Header="Дата покупки" DisplayMemberBinding="{Binding Path=BuyTradeDate,StringFormat='dd.MM.yy HH:mm'}" />
                    <GridViewColumn Width="90" Header="Цена покупки" DisplayMemberBinding="{Binding Path=BuyPrice, StringFormat=F2}" />
                    <GridViewColumn Width="50"  Header="Объем" DisplayMemberBinding="{Binding Path=Amount}" />
                    <GridViewColumn Width="100" Header="Заявка продажи" DisplayMemberBinding="{Binding Path=SellOrder}" />
                <GridViewColumn Width="80" Header="Дата заявки" DisplayMemberBinding="{Binding Path=SellOrderDate, StringFormat='dd.MM.yy HH:mm'}" />
                    <GridViewColumn Width="100" Header="Сделка продажи" DisplayMemberBinding="{Binding Path=SellTrade, StringFormat=F0}" />
                <GridViewColumn Width="80" Header="Дата продажи" DisplayMemberBinding="{Binding Path=SellTradeDate, StringFormat='dd.MM.yy HH:mm'}" />
                    <GridViewColumn Width="90" Header="Цена продажи" DisplayMemberBinding="{Binding Path=SellPrice, StringFormat=N2}" />
                    <GridViewColumn Width="80" Header="Прибыль" DisplayMemberBinding="{Binding Path=prFit, StringFormat=F2}" />
                    <GridViewColumn Width="60" Header="Приб.%" DisplayMemberBinding="{Binding Path=prPcn, StringFormat=P2}" />
                    <GridViewColumn Width="+60" Header="Приб.год." DisplayMemberBinding="{Binding Path=prAnn, StringFormat=P2}" />
            </GridView>
            </ListView.View>
        </ListView>

и так же выводим его в TestingPanel со начальным скрытым состоянием:
Код

                <TabItem x:Name="TabBook" Header="Book" Visibility="Hidden">
                    <robot:StrategyBook x:Name="myMEQ" MinHeight="200" Height="Auto" x:FieldModifier="public"/>
                </TabItem>


Как результат - получаем готовый интерфейс для управления работой стратегии в процессе ее выполнения.
Это именно то, что никак невозможно сделать в Tradematic в силу закрытости кода.
TLS-Plan.png 278 KB (2) TLS-Book.png 286 KB (2)
Thanks:

Лебедев Сергей

Avatar
Training
Date: 6/15/2014
Reply


Часть 8 - Универсализация интерфейсов исполнения стратегий

Сделаем еще один шаг к раз0витию S#.Shell - проведем универсализацию интерфейсов исполнения.
Как известно в стандартной версии S#.Shell тестовые стратегии выполняются на панеле (документе), где присутствуют графики и где можно добавить собственные панели (см. Часть 5), а исполнение на живом подключении проводится на панеле с сокращенным набором элементов.
Это не всегда удобно. Да и для новых стратегий тоже важно в первые месяцы их жизни следить за их состоянием с использованием графических элементов.

Для решения этой задачи вначале разбираемся как в В S#.Shell создаются панели.
Панель это на самом деле объект типа LayoutDocument из библиотеки AvalonDock, в котором можно задавать различный контент путем присвоения свойства Content типа object.
При этом в свойство Content записывается пользовательский элемент управления типа UserControl, графические элементы которого и определяют расположение и набор элементов.
Исполняемая стратегия у данного элемент управления это всего лишь свойство типа BaseShellStrategy, которое очевидно может выполняться в любом режиме - как тестовом, так и боевом.

С учетом этого для получения возможности запускать стратегии с любым типом панели делаем следующие вещи:

1. Создаем в свойствах стратегии BaseShellStrategyPropeties признак , который указываем на каком виде документа выполняется стратегия:
Код

        private enDocType _doctype = enDocType.TestDoc;
        [Category(@"Основные")]
        [DisplayName(@"Тип документа")]
        [Description(@"Тип документа стратегии")]
        [PropertyOrder(2)]
        public enDocType DocType
        {
            get { return _doctype; }
            set
            {
                _doctype = value;
                OnPropertyChanged("DocType");
            }
        }

При этом enDocType задаем как перечисляемый тип
Код

    public enum enDocType
    {
        TestDoc,
        ExecDoc
    }


2. В классе MainWindow во всех процедурах типа ExecutedAddХХХХStrategy создания исполняемых стратегий, в которых мы хотим получить выбор типа документа исполнения, задаем возможность указать желаемый вид документа:
Код

            DialogResult result;
            string selectstr = "Тип документа: сокращенный(Y) или полный(N)?";
            result = System.Windows.Forms.MessageBox.Show(selectstr, "Выбор типа документа", MessageBoxButtons.YesNo);
            if (result == System.Windows.Forms.DialogResult.Yes)
                properties.DocType = enDocType.ExecDoc;
            else
                properties.DocType = enDocType.TestDoc;


3. Далее классе MainWindow в связанных процедурах создания стратегий модифицируем создание LayoutDocument, используя указанный признак для присвоения желаемого типа документа
Код

            LayoutDocument doc=new LayoutDocument();
            doc.Content = docType == enDocType.ExecDoc? (object) new StrategyDocument {Strategy = strategy}: new TestingDocument {Strategy = strategy};
            doc.Title = strategy.Params.Name;
            doc.CanClose = false;


4. Из процедуры StartTesingStrategy выводим весь код по инициализации графических панелей в отдельную процедуру InitializePanes, которую затем безу3словно вызываем из StartTesingStrategy и условино вызываем из процедуры StartStrategy:
Код

if (_strategy.Params.Mode == enMode.Testing)
{
            Action InitPanes = null;
		    InitPanes = () =>
                    {
                        var doc = SelectedTestingDocument;
                        InitializePanes(doc);
		     };

		    if (dockManager.Dispatcher.CheckAccess())
		        InitPanes();
		    else
		        dockManager.Dispatcher.BeginInvoke(DispatcherPriority.Normal, InitPanes);
}

Причем по каким-то причинам в случае прямого вызова InitializePanes(doc) при исполнении стратегии происходят ошибки связанные с пересечением потоков, поэтому как в приведенном выше коде использовалась лямбда-функция InitPanes, которая получает тестовый документ через SelectedTestingDocument и вызывввается в потокобезопасном режиме.

И вот все готово!
Shell-Step8 (2).png 148 KB (3)
Thanks:
1 2  >

Attach files by dragging & dropping, , or pasting from the clipboard.

loading
clippy