private void OpenOrder(Cortege3 <string, int, DateTime> order) { var ticker = order.a; var side = order.b; var dealVolumeDepo = CalculateVolume(ticker); if (dealVolumeDepo == 0) { return; } float?sl = null; if (CheckDice(probStopLoss)) { var quote = QuoteStorage.Instance.ReceiveValue(ticker); if (quote != null) { var points = minStopPoints; if (CheckDice(propRandomStopStep)) { points = points + rnd.Next(maxStopPoints - minStopPoints + 1); } else { points = points + rnd.Next((maxStopPoints - minStopPoints) / stepStopPoints + 1) * stepStopPoints; } sl = side > 0 ? quote.ask : quote.bid; sl -= side * DalSpot.Instance.GetAbsValue(ticker, (float)points); } } robotContext.SendNewOrderRequest( protectedContext.MakeProtectedContext(), RequestUniqueId.Next(), new MarketOrder { Symbol = ticker, Side = side, AccountID = robotContext.AccountInfo.ID, Magic = Magic, Volume = dealVolumeDepo, ExpertComment = "RandomRobot", Comment = "", StopLoss = sl, TakeProfit = null }, OrderType.Market, 0, 0); }
private void WorkerWalletOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs ea) { ShowHideProgress(false); if (ea.Result == null) { return; } walletExplicitDetail = (WalletExplicitResponse)ea.Result; // байндинг walletCurrency = walletExplicitDetail.wallet.Currency; lblWallet.Text = walletExplicitDetail.wallet.Balance.ToStringUniformMoneyFormat() + " " + walletExplicitDetail.wallet.Currency; var summaryData = new List <Cortege3 <string, int, decimal> >(); foreach (var transfersByType in walletExplicitDetail.transfersSummary.TransfersByType) { var row = new Cortege3 <string, int, decimal> { a = EnumFriendlyName <TransfersByAccountSummary.AccountTransferType> .GetString(transfersByType.Key), b = transfersByType.Value.a, c = transfersByType.Value.b }; summaryData.Add(row); } summaryTransfersFastGrid.DataBind(summaryData); gridSubscription.DataBind(walletExplicitDetail.subscriptions ?? new List <Contract.Entity.Subscription>()); gridAccount.DataBind(walletExplicitDetail.realAccounts ?? new List <AccountShared>()); if (!transfersWorker.IsBusy) { transfersWorker.RunWorkerAsync(); } }
private void OpenOrder(Cortege3<string, int, DateTime> order) { var ticker = order.a; var side = order.b; var dealVolumeDepo = CalculateVolume(ticker); if (dealVolumeDepo == 0) return; float? sl = null; if (CheckDice(probStopLoss)) { var quote = QuoteStorage.Instance.ReceiveValue(ticker); if (quote != null) { var points = minStopPoints; if (CheckDice(propRandomStopStep)) points = points + rnd.Next(maxStopPoints - minStopPoints + 1); else points = points + rnd.Next((maxStopPoints - minStopPoints)/stepStopPoints + 1)*stepStopPoints; sl = side > 0 ? quote.ask : quote.bid; sl -= side * DalSpot.Instance.GetAbsValue(ticker, (float)points); } } robotContext.SendNewOrderRequest( protectedContext.MakeProtectedContext(), RequestUniqueId.Next(), new MarketOrder { Symbol = ticker, Side = side, AccountID = robotContext.AccountInfo.ID, Magic = Magic, Volume = dealVolumeDepo, ExpertComment = "RandomRobot", Comment = "", StopLoss = sl, TakeProfit = null }, OrderType.Market, 0, 0); }
private void BuildChannel(List <CandleData> candles, int from) { var channel = new Cortege3 <PointD, PointD, PointD> { }; var state = ChannelStateInfo.НетКанала; switch (ChannelState) { case ChannelStateInfo.НетКанала: // канала еще нет, ищем сначала по верхним точкам, если не находим, пробуем по нижним найти var channelhigh = GetHighLineChannel(candles, from); var channellow = GetLowLineChannel(candles, from); if ((channelhigh.a == new PointD(0, 0) || channelhigh.b == new PointD(0, 0)) && (channellow.a == new PointD(0, 0) || channellow.b == new PointD(0, 0))) { break; } if (channelhigh.a == new PointD(0, 0) || channelhigh.b == new PointD(0, 0)) { state = ChannelStateInfo.ПостроенПоМинимумам; channel = channellow; break; } if ((channellow.a == new PointD(0, 0) || channellow.b == new PointD(0, 0))) { state = ChannelStateInfo.ПостроенПоМаксимумам; channel = channelhigh; break; } if (channelhigh.b.X > channellow.b.X || (channelhigh.b.X == channellow.b.X && channelhigh.a.X > channellow.a.X)) { state = ChannelStateInfo.ПостроенПоМаксимумам; channel = channelhigh; break; } state = ChannelStateInfo.ПостроенПоМинимумам; channel = channellow; break; case ChannelStateInfo.ПостроенПоМаксимумам: channel = GetLowLineChannel(candles, from); if (channel.a == new PointD(0, 0) || channel.b == new PointD(0, 0)) { return; } state = ChannelStateInfo.ПостроенПоМинимумам; break; case ChannelStateInfo.ПостроенПоМинимумам: channel = GetHighLineChannel(candles, from); if (channel.a == new PointD(0, 0) || channel.b == new PointD(0, 0)) { return; } state = ChannelStateInfo.ПостроенПоМаксимумам; break; } if (lastIndexB == 0 && lastIndexA == 0) { lastIndexA = channel.a.X; lastIndexB = channel.b.X; } if (lastIndexB == channel.b.X && lastIndexA < channel.a.X) { // найденный канал старше текущего, игнорируем return; } if (!ShowAllChannels && lastIndexB > channel.b.X) { // найденный канал уже старый, игнорируем его channel = currChannel; } var a = channel.a; var b = channel.b; var k = (b.Y - a.Y) / (b.X - a.X); // стираем старый канал if (!ShowAllChannels) { series.data.Clear(); } // находим точку на текущей свече + 1, туда и продлим канал var bx = 0; if (series.data.Count == 0) { bx = candles.Count; } else { bx = (int)b.X + CountAfter + CountForward; } if (TradePoints) { // просто покажем точки входа и все // посчитаем точки входа если они есть for (var i = (int)b.X + CountAfter + 1; i < b.X + CountAfter + CountForward; i++) { if (candles.Count <= i) { break; } var highY = b.Y + k * (i - b.X); var lowY = channel.c.Y + k * (i - channel.c.X); if (highY <= candles[i].high && highY >= candles[i].low) { var detailed = string.Format("S {0}", highY); var tip = new AsteriskTooltip(detailed, detailed) { Price = (float)highY, CandleIndex = i, DateStart = candles[i].timeOpen, Sign = "e", Shape = AsteriskTooltip.ShapeType.СтрелкаВниз, ColorFill = Color.Pink, ColorLine = Color.Black, ColorText = Color.Black }; commentSeries.data.Add(tip); break; } if (lowY >= candles[i].low && lowY <= candles[i].high) { var detailed = string.Format("B {0}", lowY); var tip = new AsteriskTooltip(detailed, detailed) { Price = (float)lowY, CandleIndex = i, DateStart = candles[i].timeOpen, Sign = "e", Shape = AsteriskTooltip.ShapeType.СтрелкаВверх, ColorFill = Color.Green, ColorLine = Color.Black, ColorText = Color.Black }; commentSeries.data.Add(tip); break; } } } else { var line = new TrendLine { LineColor = state == ChannelStateInfo.ПостроенПоМаксимумам ? ClUpLine : ClDownLine, LineStyle = TrendLine.TrendLineStyle.Отрезок }; // линия по двум экстремумам line.linePoints.Add(new PointD(a.X - countBefore, b.Y + k * (a.X - countBefore - b.X))); line.linePoints.Add(new PointD(bx, b.Y + k * (bx - b.X))); series.data.Add(line); line = new TrendLine { LineColor = state == ChannelStateInfo.ПостроенПоМаксимумам ? ClUpLine : ClDownLine, LineStyle = TrendLine.TrendLineStyle.Отрезок }; var c = channel.c; // линия по одному экстремуму line.linePoints.Add(new PointD(a.X - countBefore, c.Y + k * (a.X - countBefore - c.X))); line.linePoints.Add(new PointD(bx, c.Y + k * (bx - c.X))); series.data.Add(line); } ChannelState = state; currChannel = channel; lastIndexA = currChannel.a.X; lastIndexB = currChannel.b.X; }
private void WorkerWalletOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs ea) { ShowHideProgress(false); if (ea.Result == null) return; walletExplicitDetail = (WalletExplicitResponse) ea.Result; // байндинг walletCurrency = walletExplicitDetail.wallet.Currency; lblWallet.Text = walletExplicitDetail.wallet.Balance.ToStringUniformMoneyFormat() + " " + walletExplicitDetail.wallet.Currency; var summaryData = new List<Cortege3<string, int, decimal>>(); foreach (var transfersByType in walletExplicitDetail.transfersSummary.TransfersByType) { var row = new Cortege3<string, int, decimal> { a = EnumFriendlyName<TransfersByAccountSummary.AccountTransferType>.GetString(transfersByType.Key), b = transfersByType.Value.a, c = transfersByType.Value.b }; summaryData.Add(row); } summaryTransfersFastGrid.DataBind(summaryData); gridSubscription.DataBind(walletExplicitDetail.subscriptions ?? new List<Contract.Entity.Subscription>()); gridAccount.DataBind(walletExplicitDetail.realAccounts ?? new List<AccountShared>()); if (!transfersWorker.IsBusy) transfersWorker.RunWorkerAsync(); }
private void BuildChannel(List<CandleData> candles, int from) { var channel = new Cortege3<PointD, PointD, PointD> { }; var state = ChannelStateInfo.НетКанала; switch (ChannelState) { case ChannelStateInfo.НетКанала: // канала еще нет, ищем сначала по верхним точкам, если не находим, пробуем по нижним найти var channelhigh = GetHighLineChannel(candles, from); var channellow = GetLowLineChannel(candles, from); if ((channelhigh.a == new PointD(0, 0) || channelhigh.b == new PointD(0, 0)) && (channellow.a == new PointD(0, 0) || channellow.b == new PointD(0, 0))) break; if (channelhigh.a == new PointD(0, 0) || channelhigh.b == new PointD(0, 0)) { state = ChannelStateInfo.ПостроенПоМинимумам; channel = channellow; break; } if ((channellow.a == new PointD(0, 0) || channellow.b == new PointD(0, 0))) { state = ChannelStateInfo.ПостроенПоМаксимумам; channel = channelhigh; break; } if (channelhigh.b.X > channellow.b.X || (channelhigh.b.X == channellow.b.X && channelhigh.a.X > channellow.a.X)) { state = ChannelStateInfo.ПостроенПоМаксимумам; channel = channelhigh; break; } state = ChannelStateInfo.ПостроенПоМинимумам; channel = channellow; break; case ChannelStateInfo.ПостроенПоМаксимумам: channel = GetLowLineChannel(candles, from); if (channel.a == new PointD(0, 0) || channel.b == new PointD(0, 0)) return; state = ChannelStateInfo.ПостроенПоМинимумам; break; case ChannelStateInfo.ПостроенПоМинимумам: channel = GetHighLineChannel(candles, from); if (channel.a == new PointD(0, 0) || channel.b == new PointD(0, 0)) return; state = ChannelStateInfo.ПостроенПоМаксимумам; break; } if (lastIndexB == 0 && lastIndexA == 0) { lastIndexA = channel.a.X; lastIndexB = channel.b.X; } if (lastIndexB == channel.b.X && lastIndexA < channel.a.X) { // найденный канал старше текущего, игнорируем return; } if (!ShowAllChannels && lastIndexB > channel.b.X) { // найденный канал уже старый, игнорируем его channel = currChannel; } var a = channel.a; var b = channel.b; var k = (b.Y - a.Y) / (b.X - a.X); // стираем старый канал if (!ShowAllChannels) series.data.Clear(); // находим точку на текущей свече + 1, туда и продлим канал var bx = 0; if (series.data.Count == 0) bx = candles.Count; else bx = (int)b.X + CountAfter + CountForward; if (TradePoints) { // просто покажем точки входа и все // посчитаем точки входа если они есть for (var i = (int)b.X + CountAfter + 1; i < b.X + CountAfter + CountForward; i++) { if (candles.Count <= i) break; var highY = b.Y + k*(i - b.X); var lowY = channel.c.Y + k*(i - channel.c.X); if (highY <= candles[i].high && highY >= candles[i].low) { var detailed = string.Format("S {0}", highY); var tip = new AsteriskTooltip(detailed, detailed) { Price = (float)highY, CandleIndex = i, DateStart = candles[i].timeOpen, Sign = "e", Shape = AsteriskTooltip.ShapeType.СтрелкаВниз, ColorFill = Color.Pink, ColorLine = Color.Black, ColorText = Color.Black }; commentSeries.data.Add(tip); break; } if (lowY >= candles[i].low && lowY <= candles[i].high) { var detailed = string.Format("B {0}", lowY); var tip = new AsteriskTooltip(detailed, detailed) { Price = (float)lowY, CandleIndex = i, DateStart = candles[i].timeOpen, Sign = "e", Shape = AsteriskTooltip.ShapeType.СтрелкаВверх, ColorFill = Color.Green, ColorLine = Color.Black, ColorText = Color.Black }; commentSeries.data.Add(tip); break; } } } else { var line = new TrendLine { LineColor = state == ChannelStateInfo.ПостроенПоМаксимумам ? ClUpLine : ClDownLine, LineStyle = TrendLine.TrendLineStyle.Отрезок }; // линия по двум экстремумам line.linePoints.Add(new PointD(a.X - countBefore, b.Y + k*(a.X - countBefore - b.X))); line.linePoints.Add(new PointD(bx, b.Y + k*(bx - b.X))); series.data.Add(line); line = new TrendLine { LineColor = state == ChannelStateInfo.ПостроенПоМаксимумам ? ClUpLine : ClDownLine, LineStyle = TrendLine.TrendLineStyle.Отрезок }; var c = channel.c; // линия по одному экстремуму line.linePoints.Add(new PointD(a.X - countBefore, c.Y + k*(a.X - countBefore - c.X))); line.linePoints.Add(new PointD(bx, c.Y + k*(bx - c.X))); series.data.Add(line); } ChannelState = state; currChannel = channel; lastIndexA = currChannel.a.X; lastIndexB = currChannel.b.X; }
public override List<string> OnQuotesReceived(string[] names, CandleDataBidAsk[] quotes, bool isHistoryStartOff) { CandleDataBidAsk quote = null; for (var i = 0; i < names.Length; i++) { if (names[i] != ticker) continue; quote = quotes[i]; break; } if (quote == null) return null; var candle = packer.UpdateCandle(quote); //if (candles.Count == 0) return null; if (candle == null) { // тут надо производить текущие действия с открытыми позициям // сопроводить сделки, коли треба... еще как треба } else { candles.Add(candle); waitNextQuoteFlag = false; } // если это период "разгона" конвейера if (isHistoryStartOff) return null; if (candles.Count < CandlesInHistory) return null; // не достаточно накоплено свечек List<MarketOrder> positions; List<PendingOrder> pendingOrders; GetMarketOrders(out positions); GetPendingOrders(out pendingOrders); var events = new List<string>(); if (positions.Count != 0) { //Logger.InfoFormat("OnQuotesReceived: сопровождаем позицию, данные quote bid={0} ask={1} time={2}", quote.bid, quote.ask, quote.time); ManageOpenPositions(positions, pendingOrders, quote.GetCloseQuote(), ref events); } if (candle == null) { if (waitNextQuoteFlag) { //Logger.InfoFormat("OnQuotesReceived: данные quote bid={0} ask={1} time={2}", quote.bid, quote.ask, quote.time); //Logger.InfoFormat("OnQuotesReceived: ждем следующую свечу... "); return events; // цена за границей канала } if (currHighChannelPrice != 0 && currLowChannelPrice != 0 && (pendingOrders.Count != 0 || positions.Count != 0) ) return events; } else { // появилась новая свечка if (noTradeDays < candle.timeOpen) noTradeDays = candle.timeOpen; } if (positions.Count == 0) { if (CountLosses >= MaxLossSeries - 1) { CountLosses = 0; // сбрасываем счетчик после убытков // включаем режим "тишина" //noTradeDays = candle.timeOpen.AddDays(SilenceDays); ChannelState = ChannelStateInfo.НетКанала; currChannel = new Cortege3<PointD, PointD, PointD>(); } } if (noTradeDays > quote.timeClose) { return null; // включен режим тишина, ждем когда можно будет торговать } // -------------------------------------------------------------- // вот тут нужно делать только действия при появлении новой свечи // -------------------------------------------------------------- // строить только на новой свече if (candle != null) { //var s = string.Format("OnQuotesReceived: сформировалась свеча OHLC {0} {1} {2} {3}, timeopen={4} timeclose={5}", // candle.open, candle.high, candle.low, candle.close, candle.timeOpen, candle.timeClose); //Logger.Info(s); //events.Add(s); } //else //Logger.InfoFormat("OnQuotesReceived: полный проход при несформированной свечке"); //Logger.InfoFormat("OnQuotesReceived: данные quote bid={0} ask={1} time={2}", quote.bid, quote.ask, quote.time); if (positions.Count != 0 && positions[0].TakeProfit == null) { // позиция разворотная последняя, трейлингуем ее и больше ничего не делаем ChannelState = ChannelStateInfo.НетКанала; currChannel = new Cortege3<PointD, PointD, PointD>(); currLowChannelPrice = 0; currHighChannelPrice = 0; RemoveAllOrders(pendingOrders, PendingOrderType.Limit); if (candle != null) Logger.InfoFormat("Трейлингуется разворотная позиция id={0}", positions[0].ID); return events; } Logger.InfoFormat("ChannelState = {0}", ChannelState.ToString()); Logger.InfoFormat("highPrice={0}, lowPrice={1}", currHighChannelPrice, currLowChannelPrice); ChannelStateInfo state; var channel = TryToFindChannel(ChannelState, out state); var a = channel.a; var b = channel.b; var k = (b.Y - a.Y) / (b.X - a.X); if (lastIndexB == 0 && lastIndexA == 0) { lastIndexA = a.X; lastIndexB = b.X; } if (lastIndexB > b.X || (lastIndexB == channel.b.X && lastIndexA > channel.a.X)) { // найденный канал уже старый, игнорируем его channel = currChannel; } else ChannelState = state; currChannel = channel; lastIndexA = currChannel.a.X; lastIndexB = currChannel.b.X; // теперь определяем текущие точки границ канала // по двум экстремумам a = currChannel.a; b = currChannel.b; k = (b.Y - a.Y) / (b.X - a.X); var price1 = b.Y + k*(candles.Count - b.X); // по одному экстремуму var c = channel.c; var price2 = c.Y + k*(candles.Count - c.X); if (price1 > price2) { currHighChannelPrice = price1; currLowChannelPrice = price2; } else { currHighChannelPrice = price2; currLowChannelPrice = price1; } SaveLog(string.Format("текущий канал - верхняя цена {0}, нижняя цена {1}, ChannelState={2}", currHighChannelPrice, currLowChannelPrice, ChannelState)); if (quote.close < currLowChannelPrice || quote.close > currHighChannelPrice) { waitNextQuoteFlag = true; // редкая ситуация - цена находится вне канала, возможно после разворотов и сработанного стопа SaveLog(string.Format("цена {0} находится вне канала - не торгуем", quote.close)); return events; } if (ChannelState == ChannelStateInfo.НетКанала) { SaveLog(RemoveAllOrders(pendingOrders, null) ? "нет текущего канала, все отложенные ордеры сняты" : "возникла ошибка при удалении ордеров"); return events; } // надо переставить ордера // выбираем sell limit var ord = pendingOrders.FirstOrDefault(o => o.PriceSide == PendingOrderType.Limit && o.Side == -1); var pos = positions.FirstOrDefault(p => p.Side == -1); if (ord != null) { // меняем цену ордера var res = EditOrder(ord, (decimal)currHighChannelPrice, (decimal)currLowChannelPrice); if (res != RequestStatus.OK) { Logger.ErrorFormat("SlideChannelRobot: Не могу изменить ордер №{0}, вход по цене {1}", ord.ID, ord.PriceFrom); } } else if (pos == null) { // проверка есть ли разворотная позиция на покупку, тогда не будем ставить ордер //pos = positions.FirstOrDefault(p => p.Side == 1); //if (pos == null || pos.TakeProfit.HasValue) InstallOrder(ticker, (decimal) currHighChannelPrice, (decimal) currLowChannelPrice, PendingOrderType.Limit, -1, RoundMinVolume); } // ордер может ставить только если нет открытой позиции в этом направлении либо он ставится за стопом открытой позиции if (pos != null) { //if (pos.TakeProfit != null && pos.TakeProfit != 0) // позиция не разворотная //{ pos.TakeProfit = (float) currLowChannelPrice; var res = robotContext.SendEditMarketRequest(protectedContext.MakeProtectedContext(), pos); if (res != RequestStatus.OK) { Logger.ErrorFormat( "SlideChannelRobot: Не могу выставить tp={0} на позицию №{1}, вход по цене {2}", pos.TakeProfit, pos.ID, pos.PriceEnter); } //} if (ord == null && pos.StopLoss < currHighChannelPrice) { res = InstallOrder(ticker, (decimal) currHighChannelPrice, (decimal) currLowChannelPrice, PendingOrderType.Limit, -1, RoundMinVolume); if (res != RequestStatus.OK) { Logger.ErrorFormat("SlideChannelRobot: Не могу выставить ордер по цене {0} за поджатой позицией №{1}", currHighChannelPrice, pos.ID); } } } // выбираем buy limit ord = pendingOrders.FirstOrDefault(o => o.PriceSide == PendingOrderType.Limit && o.Side == 1); pos = positions.FirstOrDefault(p => p.Side == 1); if (ord != null) { // меняем цену ордера var res = EditOrder(ord, (decimal)currLowChannelPrice, (decimal)currHighChannelPrice); if (res != RequestStatus.OK) { Logger.ErrorFormat("SlideChannelRobot: Не могу изменить ордер №{0}, вход по цене {1}", ord.ID, ord.PriceFrom); } } else if (pos == null) { // проверка есть ли разворотная позиция на покупку, тогда не будем ставить ордер //pos = positions.FirstOrDefault(p => p.Side == -1); //if (pos == null || pos.TakeProfit.HasValue) InstallOrder(ticker, (decimal) currLowChannelPrice, (decimal) currHighChannelPrice, PendingOrderType.Limit, 1, RoundMinVolume); } if (pos != null) { //if (pos.TakeProfit != null && pos.TakeProfit != 0) // позиция не разворотная //{ pos.TakeProfit = (float) currHighChannelPrice; var res = robotContext.SendEditMarketRequest(protectedContext.MakeProtectedContext(), pos); if (res != RequestStatus.OK) { Logger.ErrorFormat( "SlideChannelRobot: Не могу выставить tp={0} на позицию №{1}, вход по цене {2}", pos.TakeProfit, pos.ID, pos.PriceEnter); } //} if (ord == null && pos.StopLoss > currLowChannelPrice) { res = InstallOrder(ticker, (decimal) currLowChannelPrice, (decimal) currHighChannelPrice, PendingOrderType.Limit, 1, RoundMinVolume); if (res != RequestStatus.OK) { Logger.ErrorFormat("SlideChannelRobot: Не могу выставить ордер по цене {0} за поджатой позицией №{1}", currLowChannelPrice, pos.ID); } } } return events; }
/// <summary> /// Поиск каналов /// </summary> /// <param name="state"></param> /// <returns></returns> private Cortege3<PointD, PointD, PointD> TryToFindChannel(ChannelStateInfo currState, out ChannelStateInfo state) { state = ChannelStateInfo.НетКанала; var channel = new Cortege3<PointD, PointD, PointD> { }; switch (currState) { case ChannelStateInfo.НетКанала: // канала еще нет, ищем сначала по верхним точкам, если не находим, пробуем по нижним найти var channelhigh = GetHighLineChannel(candles, candles.Count - 1); var channellow = GetLowLineChannel(candles, candles.Count - 1); if ((channelhigh.a == new PointD(0, 0) || channelhigh.b == new PointD(0, 0)) && (channellow.a == new PointD(0, 0) || channellow.b == new PointD(0, 0))) { return currChannel; } if (channelhigh.a == new PointD(0, 0) || channelhigh.b == new PointD(0, 0)) { state = ChannelStateInfo.ПостроенПоМинимумам; return channellow; } if ((channellow.a == new PointD(0, 0) || channellow.b == new PointD(0, 0))) { state = ChannelStateInfo.ПостроенПоМаксимумам; return channelhigh; } if (channelhigh.b.X > channellow.b.X || (channelhigh.b.X == channellow.b.X && channelhigh.a.X > channellow.a.X)) { state = ChannelStateInfo.ПостроенПоМаксимумам; return channelhigh; } state = ChannelStateInfo.ПостроенПоМинимумам; return channellow; case ChannelStateInfo.ПостроенПоМаксимумам: channel = GetLowLineChannel(candles, candles.Count - 1); if (channel.a == new PointD(0, 0) || channel.b == new PointD(0, 0)) channel = currChannel; else state = ChannelStateInfo.ПостроенПоМинимумам; break; case ChannelStateInfo.ПостроенПоМинимумам: channel = GetHighLineChannel(candles, candles.Count - 1); if (channel.a == new PointD(0, 0) || channel.b == new PointD(0, 0)) channel = currChannel; else state = ChannelStateInfo.ПостроенПоМаксимумам; break; } return channel; }