S#.Shell. Manual
Atom
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





< 1 2 3 
Mikhail Sukhov

Avatar
Date: 3/11/2014
Reply


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


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

JaguarFX

Avatar
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 и его инициализатор:
Code
 
    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, каждый экземпляр которого будет содержать информацию об одной торговой линии:
Code

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


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

    // Параметры плана торговли
    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 для создания План торговли по указанным параметрам:
Code

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, которая будет срабатывать сразу полсе появления заявки:
Code

        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 заявки на продажу.
Code

        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

JaguarFX

Avatar
Date: 4/14/2014
Reply


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

---
        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(@"Количество уровней")]

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

        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;

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

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

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

        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);
        }

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

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) очевидно, что капитала может не хватить в процессе резкого падения рынка, как это было при присоединении Крыма, поэтому
а) создаем свойства стратегии для хранения размера использованного капитала и %
Code

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

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

        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
Code

        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:

JaguarFX

Avatar
Date: 4/14/2014
Reply


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

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

<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 +
Code
 
                <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 для визуализации торговой книги
Code

    <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 со начальным скрытым состоянием:
Code

                <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 (212) TLS-Book.png 286 KB (230)
Thanks:

JaguarFX

Avatar
Date: 6/15/2014
Reply


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

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

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

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

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

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

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

    public enum enDocType
    {
        TestDoc,
        ExecDoc
    }


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

            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, используя указанный признак для присвоения желаемого типа документа
Code

            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:
Code

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 (241)
Thanks:

JaguarFX

Avatar
Date: 7/15/2014
Reply


Часть 9 - Проходим АД AdoNeta, или о сохранении параметров в базе данных

Как известно, в базовой версии S#.Shell отсутствует функционал, позволяющий восстановить позиции после перезапуска робота.
В версии от КазайМазай был предложен метод сохранения в xml-файл через набор свойств базовой стратегии (OrdersByTransactionId, SecuritiesByTransactionId, TradesByTransactionId, PortfoliosByTransactionId, ExchangesByTransactionId) типа SettingsStorage.
При этом у меня при всех старании активировать данное сохранение, данный метод не заработал - при активации RestorePositionsOnStart записанные теги оказывались пустыми.
На форуме так же описан метод сохранения ордеров в txt-файл, который судя по простоте работает.

Но все же сохранение в файл при всей простоте восстановления позиций такого важного элемента в созхранении записей как ведение управленческого учета прибылей и убытков от работы робота.

Именно по этой причине целесообразно реализовать функционал сохранения истории торгов в базу данных (MSSQLSRV, MSACCESS, SQLLITE и пр.) - гибкость в последобработке данной информации позволяет автоматизировать ведение учета результатов торгов.

Рассмотрим реализацию сохранения состояния робота в базу данных основе AdoNet.

1. Создадим базовый класс StorageEngine
Данный класс будет хранить обертки для подключений. По умолчанию программируем только для одного вида базы данных - MSSQLSRV, хотя при дальнейшей проработки данного класса его можно сделать универсальным провайдером функций для работы с любым выбранным пользователем видом базы данных, для которого есть драйвера AdoNet (MSSQLSRV, MSACCESS, SQLLITE)

а) функция инициализации будет содержать переменную по проверке готовности БД IsVerified, а так же вызов заданных пользователем параметров: curStor - вид базы данных, ShellConfigDB - название каталога MSSQLSRV (файла для MSACCESS и SQLLITE)
Code

        private StorageEngine()
		{
            Name = "StorageEngine";
            IsVerified = false;
            ShellConfigDB = SettingsEngine.Instance.Properties.ShellConfigDB;
            curStor = SettingsEngine.Instance.Properties.DataStorIn;
		}


б) переменные для хранения универсальных команд создания базовых таблиц (tblOrders - таблица заявок, tblTrades - таблица сделок), добавления данных и удаления данных из этих таблиц
Code

        public const string CreateOrders = @"CREATE TABLE tblOrders " +
                                            "(StrategyId varchar(50), " +
                                            "TransactionId INT, " +
                                            "OrdId BIGINT, " +
                                            "Direction varchar(4), " +
                                            "Type varchar(10), " +
                                            "ExpiryDate date, " +
                                            "State varchar(10), " +
                                            "Status varchar(10), " +
                                            "Security varchar(20), " +
                                            "ExBoard varchar(10), " +
                                            "Portfolio varchar(10), " +
                                            "Price FLOAT, " +
                                            "Time datetime , " +
                                            "Volume int , " +
                                            "Comment varchar(50))";

        public const string CreateTrades = @"CREATE TABLE tblTrades " +
                                            "(StrategyId varchar(50), " +
                                            "TransactionId int , " +
                                            "TradeId int, " +
                                            "Price float, " +
                                            "Time datetime, " +
                                            "Volume int)";

        public const string SelectOrders =
                 @"SELECT StrategyId, OrdId, TransactionId, Direction, Type, ExpiryDate, State, Status, 
                   Security, ExBoard, Portfolio, Price, Time, Volume, Comment " +
                  @"FROM tblOrders WHERE StrategyId=@SID";

        public const string DeleteOrders =
            @"DELETE FROM tblOrders WHERE StrategyId=@SID";

        public const string SelectTrades =
            @"SELECT StrategyId, TransactionId, TradeId, Price, Time, Volume " +
            @"FROM tblTrades WHERE StrategyId=@SID and TransactionId=@TID";

        public const string DeleteTrades =
            @"DELETE FROM tblTrades WHERE StrategyId=@SID";



в) создадим ряд оберток для различных функций по работе с базами данных: в данном случае функции возвращают MSSQLSRV- специфичные объекты, и не являются универсальными обертками, но принцип ясен
Code


        public SqlConnection GetConnection()
        {
            SqlConnectionStringBuilder sqlConStr = new SqlConnectionStringBuilder();
            sqlConStr.DataSource = @"(local)";
            sqlConStr.IntegratedSecurity = true;
            sqlConStr.InitialCatalog = ShellConfigDB;
            SqlConnection result = new SqlConnection(sqlConStr.ConnectionString);
            return result;
        }

        public SqlDataAdapter OpenData()
        {
            SqlDataAdapter da1 = new SqlDataAdapter();
            return da1;
        }

        public SqlCommand SqlCmd(string sqlScmd, DbConnection sqlCon)
        {
            SqlCommand cmd1 = new SqlCommand(sqlScmd, (SqlConnection)sqlCon);
            return cmd1;
        }

        public SqlCommandBuilder BuildCmd(DbDataAdapter da)
        {
            SqlCommandBuilder cb1 = new SqlCommandBuilder((SqlDataAdapter)da);
            return cb1;
        }



г) создадим функцию VerifyStor, которая будет вызываться при первичном обращении к классу для проверки готовности БД к приему данных, если БД не готова - отсутствуют таблицы, то автоматом создаются новые структуры:
Code

        public void VerifyStor()
        {
            if (IsVerified) return;

            switch (curStor)
            {
                case SettingsProperties.DataStorType.MSSQLSRV:

                    SqlConnection sqlCon = GetConnection();
                    sqlCon.Open();
                    if (sqlCon.State != ConnectionState.Open)
                    {
                        MessageBox.Show("Подключение завершилось с ошибкой");
                        return;
                    }

                    var sqlStr = @"SELECT count(name)  FROM [ShellAdvanced].[sys].[tables] where name = 'tblOrders'";
                    SqlCommand sqlCmd = new SqlCommand(sqlStr, sqlCon);
                    int rowCount = (int) sqlCmd.ExecuteScalar();
                    sqlCmd.Dispose();

                    if (rowCount == 0)
                    {
                        SqlCommand sqlCmd1 = new SqlCommand(CreateOrders, sqlCon);
                        sqlCmd1.ExecuteNonQuery();
                        sqlCmd1.Dispose();

                        SqlCommand sqlCmd2 = new SqlCommand(CreateTrades, sqlCon);
                        sqlCmd2.ExecuteNonQuery();
                        sqlCmd2.Dispose();

                        SqlCommand sqlCmd3 = new SqlCommand(CreateBook, sqlCon);
                        sqlCmd3.ExecuteNonQuery();
                        sqlCmd3.Dispose();

                    }
                    sqlCon.Close();
                    break;
                 
            }

            IsVerified = true;
        }

При этом конструкция if (IsVerified) return; позволяет минимизировать время проверки за счет сохранения статуса "Проверено" в свойстве IsVerified.


2. Для однозначной идентификации сохраненной стратегии используем Strategy.Id - свойство типа GUID, которое генерится автоматически при создании новой стратегии и в силу алгоритма генерации GUID позволяет фактически уникально определить стратегию.

а) в свойства добавляем поле StrategyID для сохранения GUID в текстовом виде
Code


        private string _StrategyID;

        [DisplayName(@"Strategy ID")]
        [Description(@"Уникальный идентификатор стратегии")]
        [Category(@"Основные")]
        [PropertyOrder(7)]
        public string StrategyID
        {
            get { return _StrategyID; }
            set
            {
                _StrategyID = value;
                OnPropertyChanged("StrategyID");
            }
        }


б) вносим восстановление GUID в различные функции создания стратегий а-ля AddRobotStrategy/AddRobotTestStrategy в классе MainWindow:
Code

            if (properties.StrategyID != null)
                strategy.Id = new Guid(properties.StrategyID);
            else
                properties.StrategyID = strategy.Id.ToString();


3. Теперь напишем функции сохранения и загрузки заявок/сделок из MSSQLSRV, разместив их в классе SettingsEngine

а) функция SaveToDB(Strategy str) принимает на вход стратегию, сделки и заявки которой необходимо сохранить
Code


public void SaveToDB(Strategy str)
	    {

            SqlDataAdapter sqlDa;
            DataTable dataTable;
	        SqlCommand sqlSCmd; // Select command
            SqlCommand sqlICmd; // Insert command
            SqlCommand sqlDCmd; // Delete commend
	        SqlCommandBuilder sqlCMDB;

            StorageEngine.Instance.VerifyStor(); // Проверка базы


            var sqlCon = StorageEngine.Instance.GetConnection();

            // Сохраняем сделки
                var sid = str.Id.ToString();
                var qrOrders = from r in str.MyTrades
                               select
                                   new
                                   {   StriD = sid,
                                       TID = r.Order.TransactionId,
                                       OrdId = r.Order.Id,
                                       OrdDir = r.Order.Direction.ToString(),
                                       OrdType = r.Order.Type.ToString(),
                                       r.Order.ExpiryDate,
                                       OrdState = r.Order.State.ToString(),
                                       OrdStatus = r.Order.Status.ToString(),
                                       SecId = r.Order.Security.Id,
                                       SecExch = r.Order.Security.Board.Code,
                                       Portname = r.Order.Portfolio.Name,
                                       r.Order.Price,
                                       r.Order.LastChangeTime,
                                       r.Order.Volume,
                                       r.Order.Comment
                                   };

                if (qrOrders.Count()==0) return;

                sqlDa = StorageEngine.Instance.OpenData();
                sqlSCmd = StorageEngine.Instance.SqlCmd(StorageEngine.SelectOrders, sqlCon);
                sqlSCmd.Parameters.AddWithValue("@SID", sid);
	            sqlDa.SelectCommand = sqlSCmd;

                sqlCMDB = StorageEngine.Instance.BuildCmd(sqlDa);
                sqlICmd = sqlCMDB.GetInsertCommand();

                dataTable = new DataTable();
                sqlDa.Fill(dataTable);

                sqlCon.Open();
                sqlDCmd = StorageEngine.Instance.SqlCmd(StorageEngine.DeleteOrders, sqlCon);
                sqlDCmd.Parameters.AddWithValue("@SID", sid);
                sqlDCmd.ExecuteNonQuery();
                sqlCon.Close();

                foreach (var qrOrder in qrOrders)
                {
                    DataRow rw = dataTable.NewRow();
                    rw["StrategyId"] = qrOrder.StriD;
                    rw["TransactionId"] = qrOrder.TID;
                    rw["OrdId"] = qrOrder.OrdId;
                    rw["Direction"] = qrOrder.OrdDir;
                    rw["Type"] = qrOrder.OrdType;
                    rw["ExpiryDate"] = qrOrder.ExpiryDate;
                    rw["State"] = qrOrder.OrdState;
                    rw["Status"] = qrOrder.OrdStatus;
                    rw["Security"] = qrOrder.SecId;
                    rw["ExBoard"] = qrOrder.SecExch;
                    rw["Portfolio"] = qrOrder.Portname;
                    rw["Price"] = qrOrder.Price;
                    rw["Time"] = qrOrder.LastChangeTime;
                    rw["Volume"] = qrOrder.Volume;
                    rw["Comment"] = qrOrder.Comment;
                    dataTable.Rows.Add(rw);
                }
                var cnt = sqlDa.Update(dataTable);
                str.AddInfoLog("В базу сохранено {0} заявок", cnt);
                
                dataTable.Dispose();

                // Запись сделок
                var qrTrades = from tr in str.MyTrades
                               select
                                   new
                                   {   StrId = sid,
                                       TID = tr.Order.TransactionId,
                                       tr.Trade.Id,
                                       tr.Trade.Price,
                                       tr.Trade.Time,
                                       tr.Trade.Volume};

                sqlDa = StorageEngine.Instance.OpenData();
                sqlSCmd = StorageEngine.Instance.SqlCmd(StorageEngine.SelectTrades, sqlCon);
                sqlSCmd.Parameters.AddWithValue("@SID", sid);
                sqlSCmd.Parameters.AddWithValue("@TID", "");
                sqlDa.SelectCommand = sqlSCmd;

                sqlCMDB = StorageEngine.Instance.BuildCmd(sqlDa);
                sqlICmd = sqlCMDB.GetInsertCommand();

                dataTable = new DataTable();
                sqlDa.Fill(dataTable);
                
                // Удаляем старые записи
                sqlCon.Open();
                sqlDCmd = StorageEngine.Instance.SqlCmd(StorageEngine.DeleteTrades, sqlCon);
                sqlDCmd.Parameters.AddWithValue("@SID", sid);
                sqlDCmd.ExecuteNonQuery();
                sqlCon.Close();

                foreach (var qrTrade in qrTrades)
                {
                    DataRow rw = dataTable.NewRow();
                    rw["StrategyId"] = qrTrade.StrId;
                    rw["TransactionId"] = qrTrade.TID;
                    rw["TradeId"] = qrTrade.Id;
                    rw["Price"] = qrTrade.Price;
                    rw["Time"] = qrTrade.Time;
                    rw["Volume"] = qrTrade.Volume;
                    dataTable.Rows.Add(rw);
                }
                var cntn = sqlDa.Update(dataTable);

                str.AddInfoLog("В базу сохранено {0} сделок", cntn);         
                
	    }


б) функция LoadFromDB (Strategy str) принимает на вход стратегию, сделки и заявки которой необходимо загрузить
Code

	    public void LoadFromDB(Strategy str)
          {

            var sqlCon = StorageEngine.Instance.GetConnection();
            SqlDataAdapter sqlDa;
            SqlCommand sqlSCmd; // Select command
            DataTable TableOrders = new DataTable();
            DataTable TableTrades = new DataTable();
            DataTable TableBook = new DataTable();


                var sid = str.Id.ToString();
                sqlDa = StorageEngine.Instance.OpenData();
                sqlSCmd = StorageEngine.Instance.SqlCmd(StorageEngine.SelectOrders, sqlCon);
	            sqlSCmd.Parameters.AddWithValue("@SID", sid);
                sqlDa.SelectCommand = sqlSCmd;
                sqlDa.Fill(TableOrders);

                str.AddInfoLog("Из базы загружено {0} заявок", TableOrders.Rows.Count.ToString());    
	            if (TableOrders.Rows.Count == 0) return;
                
                foreach (DataRow rw in TableOrders.Rows)
                {
                    long tid = (int)rw["TransactionId"];
                    var order = new Order() { TransactionId = tid };
                    long oid = (long)rw["OrdId"];
                    order.Id = oid;
	                if (order != null)
	                {
	                    order.Connector = str.Connector;
	                    var security = new Security() {Id = (string) rw["Security"]};
	                    if (security != null)
	                    {
	                        var exchange = new ExchangeBoard() {Code = (string)rw["ExBoard"]};
	                        if (exchange != null) 
                                security.Board = exchange;
	
                            security.Connector = str.Connector;
	                        order.Security = security;
	                    }
                        
                        OrderTypes OrdType;
                        OrderTypes.TryParse((string)rw["Type"], out OrdType);
                        order.Type = OrdType;

                        Sides OrderDir;
                        Sides.TryParse((string)rw["Direction"], out OrderDir);
                        order.Direction = OrderDir;

                        OrderStates OrdState;
                        OrderStates.TryParse((string)rw["State"],out OrdState);
	                    order.State = OrdState;

	                    order.Portfolio = new Portfolio() {Name = (string)rw["Portfolio"]};
                        order.ExpiryDate = (DateTime)rw["ExpiryDate"];
	                    order.Price = (decimal)(double)rw["Price"];
                        order.Volume = (decimal)(int)rw["Volume"];
                        order.Time = (DateTime)rw["Time"];
                        order.LastChangeTime = (DateTime)rw["Time"];
                        if (! DBNull.Value.Equals(rw["Comment"]))
                            { order.Comment = (string)rw["Comment"]; } // DBNUll to String

                        var myTrades = new List<MyTrade>(); // Все сделки по заявке с номером tid
                        sqlSCmd = StorageEngine.Instance.SqlCmd(StorageEngine.SelectTrades, sqlCon);
                        sqlSCmd.Parameters.AddWithValue("@SID", sid);
                        sqlSCmd.Parameters.AddWithValue("@TID", tid);
                        var sqlTDa = StorageEngine.Instance.OpenData();
                        sqlDa.SelectCommand = sqlSCmd;
                        sqlDa.Fill(TableTrades);

                        foreach (DataRow tr in TableTrades.Rows)
	                     {
                                var myTrade = new MyTrade(); 
	                            myTrade.Order = order;
                                var thisTrade = new Trade() { Id = (long)(int)tr["TradeId"], Price = (decimal)(double)tr["Price"], Time = (DateTime)tr["Time"], Volume = (decimal)(int)tr["Volume"] };
                                myTrade.Trade = thisTrade;
	                            myTrade.Trade.OrderDirection = order.Direction;
                                myTrade.Trade.Security = security;
	                            myTrade.Trade.Status = 0;
	                            myTrades.Add(myTrade);

	                        }

	                    try
	                    {
                            str.AttachOrder(order, myTrades);
	                    }
	                    catch (Exception ex)
	                    {
	                          Debug.WriteLine(ex.Message);
	                    }
                        TableTrades.Clear();   

                    }
                }
                TableOrders.Dispose();
                TableTrades.Dispose();          	               

            
	    }


в) тут же создаем универсальный загрузчик заявок и сделок на панели тестового документа, который как научились в Части 8 использовать как для тестовых прогонов, так и для реального запуска робота.
Code

        public void AddHistOrdersPane(TestingDocument doc)
        {
            Strategy str = doc.Strategy;

            var _myOrders = doc.TestingPanel.myOrders;
            _myOrders.Orders.AddRange(str.Orders);

            var _myTrades = doc.TestingPanel.myTrades;
            _myTrades.Trades.AddRange(str.MyTrades);
	    }


4. Вызываем из нужных мест функции SaveToDB/LoadFromDB

а) запиливаем вызов SaveToDB в функцию SaveStrategies, проверяя при этом наличие в параметрах стратегии установленного свойства RestorePositionsOnStart
Code

                    var StrToSave = _documents.Keys.Where(str => str.Params.RestorePositionsOnStart).ToList();
                    foreach (BaseShellStrategy strategy in StrToSave )
		                SettingsEngine.Instance.SaveToDB(strategy );
		           


б) аналогично запаиваем вызов SaveToDB в правила WhenOrderRegistered / WhenNewMyTrades

в) запихиваем вызов LoadFromDB в различные функции создания стратегий а-ля AddRobotStrategy/AddRobotTestStrategy в классе MainWindow:
Code

            if (strategy.Params.RestorePositionsOnStart) SettingsEngine.Instance.LoadFromDB(strategy);



5. Последний штрих - создаем в параметрах S#.Shell (класс SettingsProperties) свойства для сохранения переменных curStor - вид базы данных, ShellConfigDB - название каталога MSSQLSRV (файла для MSACCESS и SQLLITE)

Code
 
         public enum DataStorType {SQLLITE,MSSQLSRV}

        private DataStorType _dataStor;
        [Category(@"Сохранение")]
        [DisplayName(@"База данных")]
        [Description(@"Вариант сохранения данных")]
        [PropertyOrder(1)]
        public DataStorType DataStorIn
        {
            get { return _dataStor; }
            set
            {
                _dataStor = value;
                OnPropertyChanged("DataStorIn");
            }
        }

        private string _ShellConfigDB;
        [Category(@"Сохранение")]
        [DisplayName(@"Название базы")]
        [Description(@"Название базы данных или имя файла")]
        [PropertyOrder(2)]
        public string ShellConfigDB
        {
            get { return _ShellConfigDB; }
            set
            {
                _ShellConfigDB = value;
                OnPropertyChanged("ShellConfigDB");
            }
        }



И вуаля - сохранение в базу MSSQLSRV работает как часы!
Выглядит все просто, но чтобы пройти реально этот Ад, мне потребовалось 2 месяца арбайтена и штудирена))

Аналогично можно сохранять любые другие свойства стратегии, например PnL. Хотя тут я пошел другим путем и сделал динамическое восстановление PnL из сохраненных сделок.
Saved orders.png 246 KB (210)
Thanks:

JaguarFX

Avatar
Date: 10/5/2014
Reply


Часть 10 - Обеспечение стабильной работы робота

Итак после всех мероприятий робот работает стабильно, торгует и восстанавливает при сбоях позиции - и самое главное - зарабатывает деньги.
Но только КОГДА работает!

И тут возникают две дополнительные проблемы с обеспечением стабильности работы.

1. Автоматический запуск и начало работы
Торги на ММВБ начинаются в 10 и завершаются в 23:45, соответственно ставим задачу на автозапуск ПК в 09:55 и погружаем ПК на ночь в соню.
И тут утром обнаруживается, что по истечении 5 минут работы система автоматически переходит в режим сна!
При этом настройки питания - "высокая производительность", т.е. никаких отключений по времени.
Начинаем разбираться и выясняем, что при использовании SSD-накопителей "умные" драйверы Windows8 мониторят действия пользователя (мыши, клавиатуры) и при их отсутствии все равно усыпляют ПК. Отключить это невозможно.
Итак - лечим это через С#.

1) Создаем класс MouseOperations, в который выносим основные функции эмуляции управления мышью:
- SetCursorPosition,
- GetCursorPosition,
- MouseEvent.

Code

class MouseOperations
    {
        [Flags]
        public enum MouseEventFlags
        {
            LeftDown = 0x00000002,
            LeftUp = 0x00000004,
            MiddleDown = 0x00000020,
            MiddleUp = 0x00000040,
            Move = 0x00000001,
            Absolute = 0x00008000,
            RightDown = 0x00000008,
            RightUp = 0x00000010
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MousePoint
        {
            public int X;
            public int Y;

            public MousePoint(int x, int y)
            {
                X = x;
                Y = y;
            }
        }


        [DllImport("user32.dll", EntryPoint = "SetCursorPos")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetCursorPos(int X, int Y);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetCursorPos(out MousePoint lpMousePoint);

        [DllImport("user32.dll")]
        private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

        public static void SetCursorPosition(int X, int Y)
        {
            SetCursorPos(X, Y);
        }

        public static void SetCursorPosition(MousePoint point)
        {
            SetCursorPos(point.X, point.Y);
        }

        public static MousePoint GetCursorPosition()
        {
            MousePoint currentMousePoint;
            var gotPoint = GetCursorPos(out currentMousePoint);
            if (!gotPoint) { currentMousePoint = new MousePoint(0, 0); }
            return currentMousePoint;
        }

        public static void MouseEvent(MouseEventFlags value)
        {
            MousePoint position = GetCursorPosition();

            mouse_event
                ((int)value,
                 position.X,
                 position.Y,
                 0,
                 0);
        }
    }

При этом данные функции нужно делать именно производными от системной библиотеки user32.dll, т.к.
использование System.Windows.Forms.Cursor и System.Windows.Input.Cursor не дет нужного эффекта - это все го лишь функции отрисовки изображения курсора, а нам нужны функции эмуляции работы мыши.

2) в этом же классе создаем функцию отрисовки движения мыши по экрану:
Code

public static void BresenhamCircle(int x0, int y0, int radius)
        {
            int x = radius;
            int y = 0;
            int radiusError = 1 - x;
            while (x >= y)
            {
                
                SetCursorPosition(x + x0, y + y0);
                SetCursorPosition(y + x0, x + y0);
                SetCursorPosition(-x + x0, y + y0);
                

                y++;
                if (radiusError < 0)
                {
                    radiusError += 2 * y + 1;
                }
                else
                {
                    x--;
                    radiusError += 2 * (y - x + 1);
                }
            }
        }


3) вызываем функцию из какого-либо переодического события, например из функции OnTimeChanged(), по которой робот проверяет время работы стратегии
Code

var isContain = Helper.Contains(strategy.Params.Schedule, time);
if (isContain) MouseOperations.BresenhamCircle(200, 200, 200); 


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


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

1) устанавливаем NU-get пакет Hardcodet.NotifyIcon.Wpf, который обеспечивает работы с иконками в системной трее.

2) создаем пользовательский словарь ресурсов SysTrayResources.xaml, в котором создаем TaskbarIcon - объект иконки с системной трее, и его контекстное меню
Code

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:tb="http://www.hardcodet.net/taskbar"
                    xmlns:robot="clr-namespace:Robot">

    
  <ContextMenu x:Shared="false" x:Key="SysTrayMenu">
        <MenuItem Header="Show Window" Command="{Binding ShowWindowCommand}" />
        <MenuItem Header="Hide Window" Command="{Binding HideWindowCommand}" />
        <Separator />
        <MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}" />
    </ContextMenu>


    <tb:TaskbarIcon x:Key="SysTrayIcon"
                    IconSource="/Service/stocksharp.ico"
                    ToolTipText=""
                    DoubleClickCommand="{Binding ShowWindowCommand}"
                    ContextMenu="{StaticResource SysTrayMenu}">

        <tb:TaskbarIcon.DataContext>
            <robot:SysTrayViewModel />
        </tb:TaskbarIcon.DataContext>
    </tb:TaskbarIcon>

</ResourceDictionary>

Как видно из кода пункты контекстного меню вызывают функции ShowWindowCommand/HideWindowCommand/ExitApplicationCommand, которые расположены в классе SysTrayViewModel.

2) данный словарь необходимо вызвать из App.xaml, что объект отрисовался сразу после запуска программы
Code

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Service\SysTrayResources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>


3) далее создаем класс SysTrayViewModel, в котором размешаем команды скрытия/отображения робота:
Code

    public class SysTrayViewModel
    {

        public ICommand ShowWindowCommand
        {
            get
            {
                return new DelegateCommand
                {
                    CanExecuteFunc = () => Application.Current.MainWindow.Visibility == Visibility.Hidden,
                    CommandAction = () =>
                    {
                   if (Application.Current.MainWindow == null) Application.Current.MainWindow = new MainWindow();
                   else Application.Current.MainWindow.Visibility = Visibility.Visible;
                    }
                };
            }
        }

        public ICommand HideWindowCommand
        {
            get
            {
                return new DelegateCommand
                {
                    CommandAction = () => Application.Current.MainWindow.Visibility = Visibility.Hidden,
                    CanExecuteFunc = () => Application.Current.MainWindow.Visibility == Visibility.Visible
                };
            }
        }

        public ICommand ExitApplicationCommand
        {
            get
            {
                return new DelegateCommand { CommandAction = () => Application.Current.Shutdown() };
            }
        }
    }


При этом нужно пояснить что DelegateCommand - это класс-делегат для ускоренного создания выполняемых команд
Code

    public class DelegateCommand : ICommand
    {
        public Action CommandAction { get; set; }
        public Func<bool> CanExecuteFunc { get; set; }

        public void Execute(object parameter)
        {
            CommandAction();
        }

        public bool CanExecute(object parameter)
        {
            return CanExecuteFunc == null || CanExecuteFunc();
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }


Итак - контекстное меню прекрасно работает.
Но этого мало))

4) Создаем в настройках свойство Starthide, и помещаем в процедуру MainWindow() простую проверку на необходимость сразу скрыть робота после старта:
Code

            if (SettingsEngine.Instance.Properties.Starthide)
                Application.Current.MainWindow.Visibility = Visibility.Hidden;


5) Ставим для отображения робота ПАРОЛЬ - как же без него!
Для этого создаем маленькую пользовательскую форму UIpassword, в которой размещает поле для вода текста типа PasswordBox
Code

    <Grid >
        <PasswordBox Name="pwdBox" HorizontalAlignment="Left" Margin="17,20,0,0" VerticalAlignment="Top" Width="145" 
                     PasswordChar="#" PasswordChanged="PasswordChangedHandler" Height="29"/>
        <Label Content="Password please" HorizontalAlignment="Left" Height="24" Margin="38,-4,0,0" VerticalAlignment="Top" Width="102"/>

    </Grid>


В событии PasswordChangedHandler прописываем проверку заданного нами пароля и если все ок - то отображаем основное окно робота
Code

        private void PasswordChangedHandler(object sender, RoutedEventArgs e)
        {
            //Debug.WriteLine(pwdBox.Password);
            if (pwdBox.Password == "ххххххх")
            {
                Application.Current.MainWindow.Visibility = Visibility.Visible;
            }
        }


И после этого в коде класса SysTrayViewModel вместо мгновенного открытия приложения прописываем старт формы UIpassword
Code

           CommandAction = () =>
            {
              if (Application.Current.MainWindow == null) Application.Current.MainWindow = new MainWindow();
              var newWnd = new UIpassword();
              newWnd.ShowDialog();
            }
TaskBar1.png 168 KB (215) TaskBar2.png 1 MB (224)
Thanks: kornego dimtrdr
< 1 2 3 

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

loading
clippy