/// <summary> Функция находит ближайший экстремум слева, начиная поиск с левого края окна </summary> /// <param name="flatNumber">Номер объекта в списке боковиков</param> /// <returns>Свеча</returns> private _CandleStruct FindClosestExtremum(int flatNumber) { int candlesPassed = 1; FlatIdentifier currentFlat = flatList[flatNumber]; // Цикл выполняется, пока на найдётся подходящий экстремум либо не пройдёт константное число итераций while (candlesPassed < _Constants.MaxFlatLeftExtremumDistance) { candlesPassed++; int currentIndex = currentFlat.bounds.left.index - candlesPassed; _CandleStruct closestExtremum = globalCandles[currentIndex]; if (globalCandles[currentIndex].index < 5) { return(globalCandles[0]); } if (closestExtremum.low < currentFlat.gMin - _Constants.FlatClassifyOffset * currentFlat.gMin && IsCandleLowerThanNearests(closestExtremum)) { return(closestExtremum); } if (closestExtremum.high > currentFlat.gMax + _Constants.FlatClassifyOffset * currentFlat.gMax && IsCandleHigherThanNearests(closestExtremum)) { return(closestExtremum); } } logger.Trace("Extremum not found"); return(globalCandles[0]); }
/// <summary> Находит свечу, определяющую сторону выхода боковика </summary> /// <param name="flatNumber">Номер боковика</param> /// <returns>Свеча снизу или сверху после движения</returns> private _CandleStruct FindLeavingCandle(int flatNumber) { FlatIdentifier currentFlat = flatList[flatNumber]; int currentIndex = currentFlat.bounds.right.index + 1; _CandleStruct result = globalCandles[currentIndex]; double priceOffset = currentFlat.mean * _Constants.LeavingCoeff; double flatUpperBound = currentFlat.SDH + priceOffset; double flatLowerBound = currentFlat.SDL - priceOffset; if (flatNumber == flatsOverall - 1) { return(globalCandles[globalCandles.Count - 1]); } while (result.time != flatList[flatNumber + 1].bounds.right.time) { result = globalCandles[currentIndex]; if (result.high > flatUpperBound || result.low < flatLowerBound) { result = globalCandles[currentIndex]; return(result); } currentIndex++; } return(result); }
/// <summary> Находит и устанавливает тейк-профит для флета, закрывающегося вниз на шорт </summary> /// <param name="i">Индекс флета</param> private void SetTakeProfitsForDownLeavingFlats(ref int i) { FlatIdentifier currentFlat = flatList[i]; FlatIdentifier nextFlat = flatList[i + 1]; int flatsToOpposite = 0; currentFlat.takeProfitCandle.deltaPrice = double.PositiveInfinity; while (currentFlat.leavingDirection == flatList[i + flatsToOpposite].leavingDirection && i + flatsToOpposite < flatsOverall - 1) { flatsToOpposite++; } for (int j = currentFlat.leavingCandle.index; j <= nextFlat.leavingCandle.index; j++) { if (globalCandles[j].low - currentFlat.leavingCandle.close < currentFlat.takeProfitCandle.deltaPrice) { _TakeProfitCandle takeProfit; takeProfit.candle = globalCandles[j]; takeProfit.deltaDistance = globalCandles[j].index - currentFlat.leavingCandle.index; takeProfit.deltaPrice = Math.Abs(currentFlat.leavingCandle.close - globalCandles[j].low); currentFlat.takeProfitCandle = takeProfit; } } flatList[i].takeProfitCandle = currentFlat.takeProfitCandle; i += flatsToOpposite; }
/// <summary> Основная функция, выполняющая поиск всех боковиков в глобальном списке свечей </summary> public void FindAllFlats() { // Как правило, globalIterator хранит в себе индекс начала окна во всём датасете for (int globalIterator = 0; globalIterator < globalCandles.Count - _Constants.NAperture * 2;) { FlatIdentifier flat = new FlatIdentifier(); flat.AssignAperture(aperture); flat.Identify(); // Определяем начальное окно // Если не определили боковик сходу if (!flat.isFlat) { Printer printer = new Printer(flat); printer.PrintReasonsApertureIsNotFlat(); globalIterator++; MoveAperture(ref globalIterator); continue; } while (flat.isFlat) { // ExpansionRate раз... for (int j = 0; j < _Constants.ExpansionRate; j++) { // ЕСЛИ не конец данных if (globalIterator + aperture.Count + 1 != globalCandles[globalCandles.Count - 1].index) { // ... расширяем окно на 1 свечу ExtendAperture(globalIterator, ref aperture); } else { flatList.Add(flat); flatsFound++; return; } } flat.AssignAperture(aperture); flat.Identify(); // Identify() вызывает SetBounds(), если isFlat == true if (flat.isFlat) { continue; } Printer printer = new Printer(flat); printer.PrintReasonsApertureIsNotFlat(); flatList.Add(flat); flatsFound++; globalIterator += aperture.Count; // Переместить итератор на следующую после найденного окна свечу MoveAperture(ref globalIterator); } } }
/// <summary> Определяет, что предшествовало боковому движению </summary> /// <param name="flat">Боковик</param> /// <param name="flatNumber">Номер боковика</param> /// <returns>Восходящий или нисходящий тренд</returns> private Direction ClassifyFormedFrom(FlatIdentifier flat, int flatNumber) { _CandleStruct closestExtremum = FindClosestExtremum(flatNumber); flat.formedFromCandle = closestExtremum; logger.Trace($"[{flat.bounds.left.time} {flat.bounds.right.time}] Closest extremum in {flat.formedFromCandle.time}"); Direction result = closestExtremum.avg > flat.mean ? Direction.Up : Direction.Down; flat.formedFrom = result; return(result); }
private void ClassifySlops(FlatIdentifier flat, double atan) { if (atan < _Constants.ArcTanThreshold && flat.formedFrom == flat.leavingDirection || atan >= _Constants.ArcTanThreshold && flat.formedFrom != flat.leavingDirection) { slopPositiveCounter++; } else { slopNegativeCounter++; } }
/// <summary> /// Функция склеивает находящиеся близко друг к другу боковики /// </summary> public void UniteFlats() { for (int i = 1; i < flatsFound; i++) { FlatIdentifier currentFlat = flatList[i]; FlatIdentifier prevFlat = flatList[i - 1]; bool areFlatsInTheSameDay = currentFlat.bounds.left.date == prevFlat.bounds.left.date; bool areFlatsTooClose = currentFlat.bounds.left.index - prevFlat.bounds.right.index <= _Constants.MinFlatGap; bool areFlatsMeansRoughlyEqual = Math.Abs(currentFlat.mean - prevFlat.mean) <= _Constants.FlatsMeanOffset * (currentFlat.mean + prevFlat.mean) * 0.5; bool isPrevFlatHasClosing = prevFlat.bounds.left.time != currentFlat.leavingCandle.time; logger.Trace($"{prevFlat.candles[0].date}: [{prevFlat.bounds.left.time} {prevFlat.bounds.right.time}] " + $"and [{currentFlat.bounds.left.time} {currentFlat.bounds.right.time}] " + $"Day = {areFlatsInTheSameDay} Distance = {areFlatsTooClose} Means = {areFlatsMeansRoughlyEqual} PrevFlatCloseAtCurr = {isPrevFlatHasClosing}"); // ЕСЛИ левая граница предыдущего и левая граница текущего находятся в пределах одного дня // И ЕСЛИ разница в свечах между левой границей текущего и правой границей предыдущего меьше ГАПА // И ЕСЛИ разница в цене между мат. ожиданиями текущего и предыдущего <= (ОФФСЕТ * среднее между мат. ожиданиями обоих боковиков) if (!areFlatsInTheSameDay || !areFlatsTooClose || !areFlatsMeansRoughlyEqual || !isPrevFlatHasClosing) { continue; } logger.Trace("Uniting"); List <_CandleStruct> newAperture = new List <_CandleStruct>(currentFlat.bounds.right.index - prevFlat.bounds.left.index); for (int j = prevFlat.bounds.left.index; j <= currentFlat.bounds.right.index; j++) { newAperture.Add(globalCandles[j]); } FlatIdentifier newFlat = new FlatIdentifier(); newFlat.AssignAperture(newAperture); newFlat.CalculateFlatProperties(); newFlat.bounds = newFlat.SetBounds(newFlat.candles[0], newFlat.candles[newFlat.candles.Count - 1]); newFlat.SetBounds(newFlat.candles[0], newFlat.candles[newFlat.candles.Count - 1]); flatList.RemoveRange(i - 1, 2); flatList.Insert(i - 1, newFlat); flatsFound--; i++; flatUnions++; } }
/// <summary> Определяет направление выхода боковика </summary> /// <param name="flat">Объект боковика</param> /// <param name="flatNumber">Номер объекта</param> /// <returns>Вниз или вверх</returns> private Direction ClassifyLeavingDirection(FlatIdentifier flat, int flatNumber) { _CandleStruct leavingCandle = FindLeavingCandle(flatNumber); flat.leavingCandle = leavingCandle; logger.Trace($"[{flat.bounds.left.time} {flat.bounds.right.time}]: Leaving to candle in {leavingCandle.time}"); if (leavingCandle.close > flat.mean) { const Direction result = Direction.Up; flat.leavingDirection = Direction.Up; return(result); } if (leavingCandle.close < flat.mean) { const Direction result = Direction.Down; flat.leavingDirection = result; return(result); } return(Direction.Neutral); }
private double GetTangentForAscending(FlatIdentifier flat) { return((flat.candles[0].index - flat.formedFromCandle.index) / (flat.formedFromCandle.low - flat.mean)); }
private double CalculateTangentFromClosestExtremum(FlatIdentifier flat) { return(flat.formedFrom == Direction.Down ? GetTangentForDescending(flat) : GetTangentForAscending(flat)); }
private double CalculateArcTanFromClosestExtremum(FlatIdentifier flat) { return(Math.Atan(CalculateTangentFromClosestExtremum(flat))); }
public Printer(FlatIdentifier flat) : this() { this.flat = flat; }