понедельник, 20 октября 2025 г.

25.10.17, DeepSeek, MassTransit, Generators, HLOC

25.10.17, DeepSeek, MassTransit,  Generators, HLOC

https://chat.deepseek.com/share/m61edwlpy7rsmcrq1v


Напиши одну простую, но очень полезную функцию. Часто при генерации CandleStick генераторы ошибаются в порядке значений HLOC. Например, в этом Вашем примере Вы ошиблись public CandleStick Generate() { // Ваша кастомная логика генерации return new CandleStick( "CUSTOM", DateTime.UtcNow, 100, 105, 99, 103, 10000 ); } CandleStick - сгенериласть неправильно. Нарушен порядок HLOC Это бывает и в самых лучших генераторах. Поэтому люди всегда делают проверку правильности порядка HLOC. При проверке, как правило, если порядок нарушен, то ошибочное значение отбрасывают и берут следующее значение HLOC. Но я всегда придерживался другой тактики. Необходимо всегда проверять порядок HLOC и НЕ ОТБРАСЫВАТЬ НЕПРАВИЛЬНОЕ ЗНАЧЕНИЕ, а просто сразу в Динамике создавать правильное значение. ТО есть сразу ставить значение на свое место. ЕСли следующее значение предназначено для уже занятого места - просто делам swap ... и так если надо несколько раз. То есть при генерации очередного значения сразу пытаться выстраивать правильный порядок - ставить значение на свое место, сдвигая значения или меняя значения swap. Переставлять в массиве значения, менять местами. Для каждого значения из очереди ты сразу пытаешься поставить значение на свое место. Попробуй напиши такую функцию.

Мы хотим создать функцию, которая принимает сгенерированные значения High, Low, Open, Close и гарантирует, что они удовлетворяют правилам:

  • High является наибольшим значением среди Open, High, Low, Close.

  • Low является наименьшим значением среди Open, High, Low, Close.

Однако, мы не хотим отбрасывать сгенерированные значения, а хотим скорректировать их, переставляя местами (swap) до тех пор, пока не получим правильный порядок.

Подход:

  1. Мы имеем четыре значения: H, L, O, C.

  2. Мы хотим, чтобы H был максимальным, L - минимальным, а O и C могут быть любыми между L и H.

План:

  • Мы можем рассматривать эти четыре значения как массив и отсортировать его так, чтобы максимальное стало High, минимальное стало Low, а два оставшихся будут Open и Close.

  • Но просто отсортировать и присвоить: High = max, Low = min, а Open и Close - оставшиеся два, не сохраняет исходные значения, а только их множества.

Однако, условие: мы не хотим менять сами значения, а только их роли (т.е. мы можем поменять местами, какое значение является High, а какое Low и т.д.).

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

Например, если мы сгенерировали:
Open = 100, High = 105, Low = 99, Close = 103 -> это правильный порядок.

Но если:
Open = 100, High = 95, Low = 99, Close = 103 -> это неправильный порядок.

Мы можем сделать так: поменять местами High и Low, если High < Low, и также убедиться, что Open и Close находятся между Low и High.

Однако, проблема в том, что иногда могут быть сгенерированы значения, которые не только имеют неправильные High и Low, но и Open или Close могут выходить за границы.

Поэтому более надежный способ - это:

  1. Найти максимальное и минимальное значение из четырех.

  2. Присвоить High = максимальное, Low = минимальное.

  3. Тогда Open и Close будут два оставшихся значения.

Но это не сохраняет исходные роли? Например, если мы сгенерировали Open, High, Low, Close, а после коррекции мы теряем, какое значение было сгенерировано как Open, а какое как Close?

В контексте свечи (CandleStick) важно, чтобы Open и Close были именно теми, которые были сгенерированы? Или же важно только чтобы были соблюдены условия: High >= Open, Close, Low <= Open, Close?

Если важно сохранить исходные Open и Close (т.е. мы не хотим менять их местами), то тогда мы не можем просто взять два оставшихся значения.

Но в условии сказано: "при генерации очередного значения сразу пытаться выстраивать правильный порядок - ставить значение на свое место, сдвигая значения или меняя значения swap".

Мы можем сделать следующее:

  • У нас есть четыре значения: H, L, O, C.

  • Мы можем проверить:
    if (H < L) swap(H, L);
    if (O < L) swap(O, L);
    if (O > H) swap(O, H);
    if (C < L) swap(C, L);
    if (C > H) swap(C, H);

Но это не гарантирует, что после этого H будет максимальным, а L - минимальным, потому что после swap O и C могут снова выйти за границы?

Пример:
Пусть исходно: H=50, L=100, O=110, C=120.
Шаг1: H<L -> swap(H,L) -> H=100, L=50.
Шаг2: O=110 > H=100 -> swap(O,H) -> O=100, H=110.
Шаг3: C=120 > H=110 -> swap(C,H) -> C=110, H=120.
Теперь: H=120, L=50, O=100, C=110 -> удовлетворяет.

Но что если у нас несколько нарушений? Например, H и L поменяны местами, и при этом O и C тоже не в порядке.

Другой подход: мы можем собрать все четыре значения в список, найти максимум и минимум, и тогда:

High = максимум
Low = минимум
А для Open и Close мы берем два оставшихся значения. Но тогда мы теряем исходное назначение Open и Close.

В контексте свечи, Open и Close должны быть именно теми, которые были сгенерированы? Или же мы можем их поменять?

В генераторе случайных свечей, обычно, Open и Close генерируются независимо, и затем определяются High и Low как максимум и минимум из Open, Close и еще двух случайных значений (которые могут быть выше или ниже). Но в нашем случае мы генерируем все четыре значения.

Если мы не хотим терять исходные Open и Close, то мы должны скорректировать только High и Low, чтобы они были максимальными и минимальными, но тогда Open и Close должны остаться без изменения? Но тогда может оказаться, что Open или Close выходят за границы [Low, High]? Это недопустимо.

Поэтому, если мы хотим сохранить Open и Close, то мы должны:

High = max(Open, Close, High)
Low = min(Open, Close, Low)

Но тогда мы меняем High и Low, но не меняем Open и Close.

Однако, в условии задачи сказано: "при генерации очередного значения сразу пытаться выстраивать правильный порядок". Это значит, что мы генерируем значения по одному и сразу расставляем их в правильные позиции.

Давайте представим, что мы генерируем значения в следующем порядке: Open, High, Low, Close.

Мы можем делать так:

  1. Сгенерировали Open -> пока что это и максимум и минимум.

  2. Сгенерировали High -> если High < Open, то swap(High, Open). Теперь High >= Open.

  3. Сгенерировали Low -> если Low > Open, то swap(Low, Open). Но теперь у нас Open может стать больше High? Поэтому нужно проверить и с High тоже.

Более безопасный подход:

Мы можем поддерживать текущий максимум и минимум из уже сгенерированных значений.

Пусть мы генерируем значения в произвольном порядке, но каждое новое значение мы сравниваем с текущими High и Low и при необходимости обновляем их.

Однако, в случае свечи, у нас есть четыре конкретных значения, и мы хотим, чтобы High был самым большим, Low - самым маленьким, а Open и Close - любыми между ними.

Итак, алгоритм:

  1. Сгенерируем все четыре значения.

  2. Затем проведем коррекцию:

    Let H = High, L = Low, O = Open, C = Close.

    // Сначала убедимся, что H и L - это максимум и минимум из всех четырех.
    Let maxVal = max(H, L, O, C);
    Let minVal = min(H, L, O, C);

    // Теперь, если H не равно maxVal, то нам нужно поменять H с тем элементом, который равен maxVal.
    // Аналогично для L и minVal.

    Но мы хотим сохранить O и C, насколько это возможно? Или мы можем менять их местами с H и L?

В условии сказано: менять местами (swap) до тех пор, пока не получим правильный порядок.

Мы можем сделать:

text
  if (H != maxVal) {
      if (O == maxVal) swap(H, O);
      else if (L == maxVal) swap(H, L);
      else if (C == maxVal) swap(H, C);
  }

  if (L != minVal) {
      if (O == minVal) swap(L, O);
      else if (H == minVal) swap(L, H); // Но после предыдущего шага H уже maxVal, так что этого не должно случиться?
      else if (C == minVal) swap(L, C);
  }

Но после этого может случиться, что O и C поменялись, и теперь у нас O и C не те, что были? Но в условии не сказано, что Open и Close должны остаться именно в том порядке, в котором были сгенерированы. Важно только чтобы High был максимальным, Low минимальным, а Open и Close - любыми между ними.

Однако, в свече важно, какое значение было Open, а какое Close, потому что это определяет цвет свечи (бычья/медвежья). Поэтому мы не можем просто поменять Open и Close местами.

Тогда давайте уточним: мы хотим, чтобы Open и Close остались теми же значениями, но если они выходят за границы [Low, High], то мы должны скорректировать High и Low, чтобы они включали Open и Close.

Но в примере генератора мы видим, что генерируются все четыре значения, и затем мы хотим, чтобы они удовлетворяли условиям:

text
  High >= Open, Close
  Low <= Open, Close

Поэтому, если мы сгенерировали значения, то мы можем:

text
  High = max(High, Open, Close);
  Low = min(Low, Open, Close);

Это гарантирует, что условия выполняются, и при этом мы не меняем Open и Close.

Но тогда High и Low могут быть изменены, и мы теряем исходные сгенерированные значения для High и Low? Но это лучше, чем иметь некорректную свечу.

Однако, в условии задачи сказано: "не отбрасывать неправильное значение, а просто сразу в динамике создавать правильное значение". Это можно интерпретировать как: мы можем менять значения местами, но не отбрасывать их.

Если мы используем подход с переназначением High и Low, то мы не теряем значения, а только меняем их роли. Например, если сгенерированный High был меньше Open, то мы делаем High = Open, а то, что было сгенерировано как High, становится Open? Но тогда мы меняем Open и High местами? Это не сохраняет исходный Open.

Другой вариант: мы оставляем Open и Close без изменения, а High и Low корректируем так, чтобы они были максимальными и минимальными из всех четырех. Но тогда мы не меняем значения местами, а просто присваиваем High и Low новые значения (максимум и минимум). Это простой и эффективный способ.

Но в условии требуется менять значения местами (swap). Поэтому, возможно, требуется именно перестановка значений между полями.

Давайте определим, что у нас есть четыре переменные: h, l, o, c.

Мы хотим, чтобы:
h было максимальным из {h, l, o, c}
l было минимальным из {h, l, o, c}

Мы можем сделать:

text
  temp = h;
  h = max(h, o, c, l);
  // но тогда мы потеряем значение h, если оно не было максимумом.

Вместо этого, мы можем сделать перестановки:

text
  if (h < o) swap(h, o);
  if (h < c) swap(h, c);
  if (h < l) swap(h, l);
  if (l > o) swap(l, o);
  if (l > c) swap(l, c);
  if (l > h) swap(l, h); // но после предыдущих шагов h уже максимум, так что этого не будет

Этот алгоритм:

text
  Шаг1: убеждаемся, что h >= o, c, l. Для этого мы последовательно сравниваем h с o, c, l и меняем местами, если h меньше.
  Шаг2: теперь h - максимум. Затем убеждаемся, что l <= o, c. Для этого сравниваем l с o и c и меняем местами, если l больше.

Но после шага1, мы могли поменять h и o, и тогда o стало тем, что было h. Затем мы сравниваем новое h (которое было o) с c и l. Это работает.

Пример:

text
  Исходно: h=50, o=100, c=80, l=60.

  Шаг1: h < o -> swap(h,o) -> h=100, o=50.
  Затем: h=100 и c=80 -> не меняем.
  Затем: h=100 и l=60 -> не меняем.

  Теперь: h=100, o=50, c=80, l=60.

  Шаг2: l=60 и o=50 -> l>o -> swap(l,o) -> l=50, o=60.
  Затем: l=50 и c=80 -> не меняем.

  Результат: h=100, l=50, o=60, c=80 -> удовлетворяет.

Но что если после шага1, мы поменяли h и l, и тогда l стало большим? Тогда на шаге2 мы снова поменяем l и o, и тогда l станет меньше.

Этот алгоритм гарантирует, что после выполнения h будет максимумом, а l - минимумом, и при этом мы сохраняем все значения, просто перераспределяя их по полям.

Однако, обратите внимание, что мы меняем значения между полями. Это значит, что значение, которое изначально было сгенерировано как Open, может оказаться в поле High, и наоборот.

Но в контексте свечи, это может быть неприемлемо, потому что Open и Close имеют особый смысл. Open - цена открытия, Close - цена закрытия, а High и Low - экстремумы за период.

Поэтому, если мы хотим сохранить смысл Open и Close (т.е. Open - это цена в начале периода, Close - в конце), то мы не можем менять их местами с High и Low.

Тогда как быть?

В этом случае, мы должны сгенерировать Open и Close, а затем сгенерировать High и Low такие, что High >= max(Open, Close) и Low <= min(Open, Close). Но тогда мы не можем генерировать High и Low независимо.

Но в условии задачи сказано, что генератор иногда ошибается в порядке HLOC. Предположим, что генератор независимо генерирует все четыре значения, и мы хотим их скорректировать, чтобы они образовали valid candle.

Если мы не можем менять Open и Close с High и Low, то мы должны:

text
  High = max(High, Open, Close);
  Low = min(Low, Open, Close);

Это гарантирует, что High и Low будут корректными, но при этом мы меняем только High и Low, а Open и Close остаются неизменными.

Но тогда мы теряем исходные значения High и Low? Да, но это необходимо для корректности.

Итак, в зависимости от требований к сохранению Open и Close, мы можем использовать два подхода:

  1. Если можно менять значения между всеми полями (т.е. не важно, в каком поле какое значение оказалось, главное чтобы были выполнены условия на High и Low), то используем алгоритм с swap.

  2. Если важно сохранить Open и Close (как цены открытия и закрытия), то мы не можем менять их с High и Low. Тогда мы корректируем только High и Low.

Поскольку в задании не указано, что Open и Close должны остаться неизменными, я предположу, что мы можем менять значения между полями.

Напишем функцию, которая корректирует свечу, переставляя значения между полями так, чтобы High был максимумом, а Low - минимумом.

Реализация:

Мы имеем класс/record CandleStick с полями: Ticker, DateTime, High, Low, Open, Close, Volume.

Мы можем создать функцию, которая принимает эти четыре значения (High, Low, Open, Close) и возвращает корректные (High, Low, Open, Close) после перестановок.

Алгоритм:

text
  void CorrectCandle(ref decimal high, ref decimal low, ref decimal open, ref decimal close)
  {
      // Сначала убедимся, что high - это максимум среди этих четырех.
      if (high < open) Swap(ref high, ref open);
      if (high < close) Swap(ref high, ref close);
      if (high < low) Swap(ref high, ref low);

      // Теперь high - максимум.

      // Затем убедимся, что low - это минимум среди оставшихся (open, close, low).
      if (low > open) Swap(ref low, ref open);
      if (low > close) Swap(ref low, ref close);
      // После этого low - минимум, а open и close - оставшиеся значения.
  }

Но нужно написать функцию Swap.

В C# для этого можно использовать:

text
  static void Swap(ref decimal a, ref decimal b)
  {
      decimal temp = a;
      a = b;
      b = temp;
  }

Затем мы можем использовать эту функцию в генераторе, чтобы каждую сгенерированную свечу корректировать.

Пример использования в генераторе:

text
  public CandleStick Generate()
  {
      decimal h = GenerateHigh();
      decimal l = GenerateLow();
      decimal o = GenerateOpen();
      decimal c = GenerateClose();

      CorrectCandle(ref h, ref l, ref o, ref c);

      return new CandleStick(Ticker, DateTime.UtcNow, h, l, o, c, Volume);
  }

Таким образом, мы гарантируем, что каждая свеча корректна.

Однако, note: в этом алгоритме мы меняем значения между полями. Это значит, что значение, сгенерированное как Open, может оказаться в поле High, и наоборот. Но в контексте свечи, это может быть допустимо, если мы считаем, что генератор может ошибаться в назначении значений.

Если же мы хотим сохранить исходное назначение (т.е. Open и Close не должны меняться с High и Low), то мы не можем использовать этот алгоритм.

В таком случае, мы должны изменить алгоритм:

text
  void CorrectCandlePreservingOpenClose(ref decimal high, ref decimal low, decimal open, decimal close)
  {
      // Мы не можем менять open и close, поэтому мы корректируем только high и low.
      high = Math.Max(high, Math.Max(open, close));
      low = Math.Min(low, Math.Min(open, close));
  }

Но тогда мы теряем исходные значения high и low, если они не удовлетворяют условиям.

Выбор подхода зависит от требований.

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

Напишем функцию CorrectCandle с использованием swap.

Также, чтобы быть уверенным, что мы не испортили свечу, мы можем добавить проверку в конце:

text
  Debug.Assert(high >= open && high >= close && low <= open && low <= close);

Давайте реализуем этот метод.

Примечание: мы не хотим менять объем (Volume) и тикер (Ticker), поэтому они остаются без изменения.

Итак, функция:

public static void CorrectCandle(ref decimal high, ref decimal low, ref decimal open, ref decimal close)
{
if (high < open) Swap(ref high, ref open);
if (high < close) Swap(ref high, ref close);
if (high < low) Swap(ref high, ref low);

text
  if (low > open) Swap(ref low, ref open);
  if (low > close) Swap(ref low, ref close);

}

public static void Swap(ref decimal a, ref decimal b)
{
var temp = a;
a = b;
b = temp;
}

Теперь мы можем использовать эту функцию в любом генераторе.

Пример использования в RandomCandleStickGenerator:

public CandleStick Generate()
{
var ticker = _tickers[_random.Next(_tickers.Length)];
var basePrice = (decimal)(_random.NextDouble() * 1000 + 100);
var variation = (decimal)(_random.NextDouble() * 10);

text
  decimal high = basePrice + variation;
  decimal low = basePrice - variation;
  decimal open = basePrice + (decimal)((_random.NextDouble() - 0.5) * 5);
  decimal close = basePrice + (decimal)((_random.NextDouble() - 0.5) * 5);

  // Корректируем свечу
  CorrectCandle(ref high, ref low, ref open, ref close);

  return new CandleStick(
      Ticker: ticker,
      DateTime: DateTime.UtcNow,
      High: high,
      Low: low,
      Open: open,
      Close: close,
      Volume: (decimal)(_random.NextDouble() * 1000000)
  );

}

Таким образом, мы гарантируем, что все свечи корректны.

Надеюсь, это то, что требуется.

Отличная идея! Создам функцию для динамической корректировки значений HLOC, которая не отбрасывает неправильные значения, а переставляет их местами до получения правильного порядка.

Реализация функции корректировки HLOC

CandleStickValidator.cs:

csharp
using System;
using System.Collections.Generic;

namespace MassTransitFastMessages
{
    public static class CandleStickValidator
    {
        /// <summary>
        /// Корректирует значения HLOC, переставляя их местами до получения правильного порядка
        /// High >= Open, Close >= Low и High >= Low
        /// </summary>
        public static void CorrectHLOC(ref decimal high, ref decimal low, ref decimal open, ref decimal close)
        {
            // Создаем список значений для удобства манипуляций
            var values = new List<decimal> { high, low, open, close };
            var indices = new List<int> { 0, 1, 2, 3 }; // 0:high, 1:low, 2:open, 3:close
            
            // Функция для перестановки значений
            void SwapValues(int index1, int index2)
            {
                (values[index1], values[index2]) = (values[index2], values[index1]);
                (indices[index1], indices[index2]) = (indices[index2], indices[index1]);
            }

            // Основной алгоритм корректировки
            bool corrected;
            do
            {
                corrected = false;
                
                // Правило 1: High должен быть максимальным среди всех значений
                int maxIndex = FindMaxIndex(values);
                if (maxIndex != 0 && values[0] < values[maxIndex])
                {
                    SwapValues(0, maxIndex);
                    corrected = true;
                    continue;
                }
                
                // Правило 2: Low должен быть минимальным среди всех значений
                int minIndex = FindMinIndex(values);
                if (minIndex != 1 && values[1] > values[minIndex])
                {
                    SwapValues(1, minIndex);
                    corrected = true;
                    continue;
                }
                
                // Правило 3: High должен быть >= Low
                if (values[0] < values[1])
                {
                    SwapValues(0, 1);
                    corrected = true;
                    continue;
                }
                
                // Правило 4: Open и Close должны быть между Low и High
                if (values[2] < values[1]) // Open < Low
                {
                    // Пытаемся найти лучшее место для Open
                    if (values[0] > values[2]) // High > Open - ставим Open на место High
                    {
                        SwapValues(0, 2);
                    }
                    else // Иначе ставим на место Low
                    {
                        SwapValues(1, 2);
                    }
                    corrected = true;
                    continue;
                }
                
                if (values[2] > values[0]) // Open > High
                {
                    // Пытаемся найти лучшее место для Open
                    if (values[1] < values[2]) // Low < Open - ставим Open на место Low
                    {
                        SwapValues(1, 2);
                    }
                    else // Иначе ставим на место High
                    {
                        SwapValues(0, 2);
                    }
                    corrected = true;
                    continue;
                }
                
                if (values[3] < values[1]) // Close < Low
                {
                    // Пытаемся найти лучшее место для Close
                    if (values[0] > values[3]) // High > Close - ставим Close на место High
                    {
                        SwapValues(0, 3);
                    }
                    else // Иначе ставим на место Low
                    {
                        SwapValues(1, 3);
                    }
                    corrected = true;
                    continue;
                }
                
                if (values[3] > values[0]) // Close > High
                {
                    // Пытаемся найти лучшее место для Close
                    if (values[1] < values[3]) // Low < Close - ставим Close на место Low
                    {
                        SwapValues(1, 3);
                    }
                    else // Иначе ставим на место High
                    {
                        SwapValues(0, 3);
                    }
                    corrected = true;
                    continue;
                }
                
            } while (corrected);
            
            // Возвращаем корректные значения
            high = values[0];
            low = values[1];
            open = values[2];
            close = values[3];
        }
        
        /// <summary>
        /// Упрощенная версия - просто обеспечивает правильный порядок без сложных перестановок
        /// </summary>
        public static void QuickCorrectHLOC(ref decimal high, ref decimal low, ref decimal open, ref decimal close)
        {
            // Создаем массив и сортируем для нахождения правильных High и Low
            var allValues = new[] { high, low, open, close };
            Array.Sort(allValues);
            
            // High - максимальное, Low - минимальное
            decimal correctHigh = allValues[3];
            decimal correctLow = allValues[0];
            
            // Оставшиеся два значения - это Open и Close
            var middleValues = new List<decimal> { allValues[1], allValues[2] };
            
            // Пытаемся сохранить оригинальные Open и Close, если они в пределах
            // Если нет - берем средние значения
            decimal correctOpen = open >= correctLow && open <= correctHigh ? open : middleValues[0];
            decimal correctClose = close >= correctLow && close <= correctHigh ? close : middleValues[1];
            
            // Если Open и Close заняли одинаковые значения, корректируем
            if (correctOpen == correctClose && middleValues.Count == 2)
            {
                if (correctOpen == middleValues[0])
                    correctClose = middleValues[1];
                else
                    correctOpen = middleValues[0];
            }
            
            high = correctHigh;
            low = correctLow;
            open = correctOpen;
            close = correctClose;
        }
        
        /// <summary>
        /// Проверяет корректность значений HLOC
        /// </summary>
        public static bool IsValidHLOC(decimal high, decimal low, decimal open, decimal close)
        {
            return high >= low && 
                   high >= open && 
                   high >= close && 
                   low <= open && 
                   low <= close;
        }
        
        private static int FindMaxIndex(List<decimal> values)
        {
            int maxIndex = 0;
            for (int i = 1; i < values.Count; i++)
            {
                if (values[i] > values[maxIndex])
                    maxIndex = i;
            }
            return maxIndex;
        }
        
        private static int FindMinIndex(List<decimal> values)
        {
            int minIndex = 0;
            for (int i = 1; i < values.Count; i++)
            {
                if (values[i] < values[minIndex])
                    minIndex = i;
            }
            return minIndex;
        }
    }
}

Обновленный генератор с валидацией

ValidatedCandleStickGenerator.cs:

csharp
using System;
using System.Timers;

namespace MassTransitFastMessages
{
    public class ValidatedCandleStickGenerator : ICandleStickGenerator
    {
        private readonly System.Timers.Timer _timer;
        private readonly Random _random = new();
        private readonly string[] _tickers = { "AAPL", "GOOGL", "MSFT", "AMZN", "TSLA" };
        private readonly bool _useQuickCorrection;
        
        public event Action<CandleStick>? OnNewCandleStick;

        public ValidatedCandleStickGenerator(bool useQuickCorrection = true)
        {
            _useQuickCorrection = useQuickCorrection;
            _timer = new System.Timers.Timer(1000);
            _timer.Elapsed += (s, e) => GenerateAndNotify();
        }

        public CandleStick Generate()
        {
            // Иногда намеренно генерируем неправильные значения для демонстрации коррекции
            bool generateInvalid = _random.Next(10) == 0; // 10% chance to generate invalid
            
            var ticker = _tickers[_random.Next(_tickers.Length)];
            var basePrice = (decimal)(_random.NextDouble() * 1000 + 100);
            var variation = (decimal)(_random.NextDouble() * 10);
            
            decimal high, low, open, close;
            
            if (generateInvalid)
            {
                // Генерируем заведомо неправильные значения
                high = basePrice - variation; // High меньше чем должно быть
                low = basePrice + variation;  // Low больше чем должно быть
                open = basePrice + variation * 2;
                close = basePrice - variation * 2;
                
                Console.WriteLine($"⚠️  Generated INVALID candle: H:{high} L:{low} O:{open} C:{close}");
            }
            else
            {
                // Генерируем нормальные значения (но они все равно могут быть неправильными)
                high = basePrice + variation;
                low = basePrice - variation;
                open = basePrice + (decimal)((_random.NextDouble() - 0.5) * 5);
                close = basePrice + (decimal)((_random.NextDouble() - 0.5) * 5);
            }
            
            // Применяем коррекцию
            if (_useQuickCorrection)
            {
                CandleStickValidator.QuickCorrectHLOC(ref high, ref low, ref open, ref close);
            }
            else
            {
                CandleStickValidator.CorrectHLOC(ref high, ref low, ref open, ref close);
            }
            
            // Проверяем результат
            if (!CandleStickValidator.IsValidHLOC(high, low, open, close))
            {
                Console.WriteLine($"❌ Correction failed! H:{high} L:{low} O:{open} C:{close}");
            }
            else if (generateInvalid)
            {
                Console.WriteLine($"✅ Corrected to: H:{high} L:{low} O:{open} C:{close}");
            }
            
            return new CandleStick(
                Ticker: ticker,
                DateTime: DateTime.UtcNow,
                High: high,
                Low: low,
                Open: open,
                Close: close,
                Volume: (decimal)(_random.NextDouble() * 1000000)
            );
        }

        public void Start() => _timer.Start();
        public void Stop() => _timer.Stop();

        private void GenerateAndNotify()
        {
            var candle = Generate();
            OnNewCandleStick?.Invoke(candle);
        }
    }
}

Пример использования в Publisher

csharp
using MassTransit;
using MassTransitFastMessages;

var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
    cfg.Host("rabbitmq://localhost", h =>
    {
        h.Username("guest");
        h.Password("guest");
    });

    cfg.ConfigureSend(s => s.UseExecute(context =>
    {
        context.Durable = false;
    }));
    
    cfg.PrefetchCount = 100;
    cfg.ConcurrentMessageLimit = 10;
});

await busControl.StartAsync();

try
{
    Console.WriteLine("=== VALIDATED CANDLE STICK PUBLISHER ===");
    Console.WriteLine("Choose generator type:");
    Console.WriteLine("1 - Validated Generator (Quick Correction)");
    Console.WriteLine("2 - Validated Generator (Advanced Correction)");
    Console.WriteLine("3 - High Performance Generator with Validation");
    
    var choice = Console.ReadLine();

    ICandleStickGenerator generator = choice switch
    {
        "2" => new ValidatedCandleStickGenerator(useQuickCorrection: false),
        "3" => new HighPerformanceGenerator(), // можно добавить валидацию и в этот генератор
        _ => new ValidatedCandleStickGenerator(useQuickCorrection: true)
    };

    long messageCount = 0;
    var timer = System.Diagnostics.Stopwatch.StartNew();

    generator.OnNewCandleStick += async (candle) =>
    {
        try
        {
            // Перед отправкой можно выполнить финальную проверку
            if (!CandleStickValidator.IsValidHLOC(candle.High, candle.Low, candle.Open, candle.Close))
            {
                Console.WriteLine($"🚨 Sending INVALID candle: {candle.Ticker}");
            }
            
            await busControl.Publish(candle, context =>
            {
                context.Durable = false;
            }).ConfigureAwait(false);
            
            messageCount++;
            if (messageCount % 50 == 0)
            {
                var elapsed = timer.Elapsed.TotalSeconds;
                var rate = messageCount / elapsed;
                Console.WriteLine($"Sent {messageCount} messages | Rate: {rate:F2} msg/sec | Last: {candle.Ticker}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Send error: {ex.Message}");
        }
    };

    Console.WriteLine("Generator started. Press any key to stop...");
    generator.Start();
    Console.ReadKey();
    generator.Stop();
    
    timer.Stop();
    Console.WriteLine($"Total sent: {messageCount} messages");
}
finally
{
    await busControl.StopAsync();
}

Ключевые особенности:

  1. Два алгоритма коррекции:

    • QuickCorrectHLOC - быстрая корректировка через сортировку

    • CorrectHLOC - продвинутая корректировка с пошаговыми перестановками

  2. Динамическое исправление: Не отбрасывает неправильные значения, а находит им правильное место

  3. Валидация: Проверка корректности до и после коррекции

  4. Тестовый режим: Иногда намеренно генерирует неправильные свечи для демонстрации работы коррекции

  5. Гибкость: Можно выбирать между скоростью (Quick) и точностью (Advanced) коррекции

Теперь ваш генератор будет всегда производить корректные свечи, даже если исходные значения были сгенерированы с ошибками в порядке HLOC!

Я думаю, что еще можно такое упрощение сделать. В генераторах нам ВСЕ РАВНО на каких местах будут стоять OC по отношению к друг другу OC ли СО. Все равно они должны стоять внутри диапазона [L,H], поэтому если мы поставим ОС на места CO - то математическая правильность Генератора не изменится. Поэтому мы можем не делать переставление CO на OC. Так мне кажется. Я этого не делал, но мне кажется это правильная идея. Как считаешь ?

Мы согласны, что порядок Open и Close не имеет значения для правильности свечи.
Главное, чтобы оба они находились в диапазоне [Low, High].
Поэтому мы можем упростить коррекцию: убедиться, что High - это максимум, Low - это минимум,
а Open и Close - любые два значения между Low и High.

Предлагаю такой упрощенный алгоритм:

  1. Найти максимальное значение среди всех четырех (High, Low, Open, Close) и присвоить его High.

  2. Найти минимальное значение среди всех четырех и присвоить его Low.

  3. Убедиться, что Open и Close находятся в диапазоне [Low, High]. Если нет, то скорректировать их,
    например, привязав к ближайшей границе или взяв случайное значение между Low и High.

Однако, в нашем случае, мы хотим сохранить исходные значения Open и Close, насколько это возможно,
но если они выходят за границы, то нам нужно их скорректировать.

Но note: если мы просто возьмем максимум и минимум из четырех, то мы можем потерять информацию о том,
какое значение было исходным High и Low. Однако, в случае ошибки генератора, мы именно это и хотим - исправить.

Упрощенная корректировка:

high = max(high, low, open, close)
low = min(high, low, open, close)

open = clamp(open, low, high) // ограничиваем open между low и high
close = clamp(close, low, high)

Но note: после этого High и Low могут быть установлены из Open или Close, что правильно.

Однако, в нашем случае, мы не хотим менять значения Open и Close местами, потому что порядок Open и Close не важен.

Реализуем функцию Clamp:

private static decimal Clamp(decimal value, decimal min, decimal max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}

Тогда алгоритм:

decimal[] arr = { high, low, open, close };
decimal newHigh = arr.Max();
decimal newLow = arr.Min();

open = Clamp(open, newLow, newHigh);
close = Clamp(close, newLow, newHigh);

high = newHigh;
low = newLow;

Но note: после этого Open и Close могут быть равны newLow или newHigh, что допустимо.

Однако, мы хотим сохранить исходные значения Open и Close, если они в пределах [newLow, newHigh].
Этот алгоритм именно это и делает.

Давайте назовем этот метод SimpleCorrectHLOC.

Также мы можем оставить более сложный метод для случаев, когда мы хотим сохранить больше исходной структуры
(например, если мы хотим, чтобы High и Low были именно из исходных High и Low, если они не противоречат условиям),
но в данном случае мы говорим, что генератор может ошибаться, поэтому мы берем максимум и минимум из всех четырех.

Предлагаю реализовать этот простой метод и использовать его.

Обновим класс CandleStickValidator:

Вы абсолютно правы! Это отличное упрощение. Порядок Open и Close действительно не имеет значения для математической корректности свечи. Главное, чтобы оба значения находились в диапазоне [Low, High].

Давайте упростим алгоритм коррекции:

Упрощенная версия коррекции HLOC

SimplifiedCandleStickValidator.cs:

csharp
using System;
using System.Linq;

namespace MassTransitFastMessages
{
    public static class SimplifiedCandleStickValidator
    {
        /// <summary>
        /// Упрощенная коррекция HLOC - порядок Open/Close не важен
        /// Главное: High >= Low, и Open/Close в пределах [Low, High]
        /// </summary>
        public static void CorrectHLOC(ref decimal high, ref decimal low, ref decimal open, ref decimal close)
        {
            // Собираем все значения
            var values = new[] { high, low, open, close };
            
            // Находим реальные High и Low
            decimal actualHigh = values.Max();
            decimal actualLow = values.Min();
            
            // Корректируем Open и Close чтобы они были в пределах [actualLow, actualHigh]
            open = Clamp(open, actualLow, actualHigh);
            close = Clamp(close, actualLow, actualHigh);
            
            // Устанавливаем корректные High и Low
            high = actualHigh;
            low = actualLow;
        }
        
        /// <summary>
        /// Ограничивает значение в заданном диапазоне
        /// </summary>
        private static decimal Clamp(decimal value, decimal min, decimal max)
        {
            if (value < min) return min;
            if (value > max) return max;
            return value;
        }
        
        /// <summary>
        /// Проверяет корректность HLOC (порядок Open/Close не проверяем)
        /// </summary>
        public static bool IsValidHLOC(decimal high, decimal low, decimal open, decimal close)
        {
            return high >= low && 
                   open >= low && open <= high &&
                   close >= low && close <= high;
        }
        
        /// <summary>
        /// Создает CandleStick с гарантированно корректными значениями HLOC
        /// </summary>
        public static CandleStick CreateValidCandleStick(string ticker, DateTime dateTime, 
            decimal high, decimal low, decimal open, decimal close, decimal volume)
        {
            CorrectHLOC(ref high, ref low, ref open, ref close);
            return new CandleStick(ticker, dateTime, high, low, open, close, volume);
        }
    }
}

Упрощенный генератор

SimpleCandleStickGenerator.cs:

csharp
using System;
using System.Timers;

namespace MassTransitFastMessages
{
    public class SimpleCandleStickGenerator : ICandleStickGenerator
    {
        private readonly System.Timers.Timer _timer;
        private readonly Random _random = new();
        private readonly string[] _tickers = { "AAPL", "GOOGL", "MSFT", "AMZN", "TSLA" };
        
        public event Action<CandleStick>? OnNewCandleStick;

        public SimpleCandleStickGenerator(int intervalMs = 1000)
        {
            _timer = new System.Timers.Timer(intervalMs);
            _timer.Elapsed += (s, e) => GenerateAndNotify();
        }

        public CandleStick Generate()
        {
            var ticker = _tickers[_random.Next(_tickers.Length)];
            var basePrice = (decimal)(_random.NextDouble() * 1000 + 100);
            var variation = (decimal)(_random.NextDouble() * 10);
            
            // Генерируем значения (возможно неправильные)
            decimal high = basePrice + (decimal)(_random.NextDouble() * 15 - 5); // ±5 от basePrice
            decimal low = basePrice + (decimal)(_random.NextDouble() * 15 - 10); // ±10 от basePrice  
            decimal open = basePrice + (decimal)(_random.NextDouble() * 8 - 4);  // ±4 от basePrice
            decimal close = basePrice + (decimal)(_random.NextDouble() * 8 - 4); // ±4 от basePrice
            
            // Иногда (10% случаев) намеренно создаем неправильную свечу
            bool makeInvalid = _random.Next(10) == 0;
            if (makeInvalid)
            {
                // Делаем High меньше Low
                (high, low) = (low, high);
                Console.WriteLine($"⚠️  Generated invalid: H:{high} < L:{low}");
            }
            
            // Автоматическая коррекция
            SimplifiedCandleStickValidator.CorrectHLOC(ref high, ref low, ref open, ref close);
            
            return new CandleStick(
                Ticker: ticker,
                DateTime: DateTime.UtcNow,
                High: high,
                Low: low,
                Open: open,
                Close: close,
                Volume: (decimal)(_random.NextDouble() * 1000000)
            );
        }

        public void Start() => _timer.Start();
        public void Stop() => _timer.Stop();

        private void GenerateAndNotify()
        {
            var candle = Generate();
            OnNewCandleStick?.Invoke(candle);
        }
    }
}

Ultra-быстрый генератор для максимальной производительности

UltraFastGenerator.cs:

csharp
using System;
using System.Threading.Channels;

namespace MassTransitFastMessages
{
    public class UltraFastGenerator : ICandleStickGenerator
    {
        private readonly Channel<CandleStick> _channel;
        private readonly Random _random = new();
        private readonly string[] _tickers = { "AAPL", "GOOGL", "MSFT", "AMZN", "TSLA" };
        private CancellationTokenSource? _cancellationTokenSource;
        
        public event Action<CandleStick>? OnNewCandleStick;

        public UltraFastGenerator()
        {
            _channel = Channel.CreateUnbounded<CandleStick>(new UnboundedChannelOptions
            {
                SingleReader = true,
                SingleWriter = true
            });
        }

        public CandleStick Generate()
        {
            var ticker = _tickers[_random.Next(_tickers.Length)];
            var basePrice = (decimal)(_random.NextDouble() * 1000 + 100);
            
            // Сверхбыстрая генерация с минимальной логикой
            decimal high = basePrice + (decimal)_random.NextDouble() * 20m;
            decimal low = basePrice - (decimal)_random.NextDouble() * 20m;
            
            // Гарантируем что high >= low (самая быстрая проверка)
            if (high < low) (high, low) = (low, high);
            
            // Open и Close просто между high и low
            decimal open = low + (decimal)_random.NextDouble() * (high - low);
            decimal close = low + (decimal)_random.NextDouble() * (high - low);
            
            return new CandleStick(
                Ticker: ticker,
                DateTime: DateTime.UtcNow,
                High: high,
                Low: low,
                Open: open,
                Close: close,
                Volume: (decimal)(_random.NextDouble() * 1000000)
            );
        }

        public void Start()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _ = Task.Run(async () => await GenerateLoop(_cancellationTokenSource.Token));
        }

        public void Stop() => _cancellationTokenSource?.Cancel();

        private async Task GenerateLoop(CancellationToken cancellationToken)
        {
            long counter = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            
            while (!cancellationToken.IsCancellationRequested)
            {
                var candle = Generate();
                await _channel.Writer.WriteAsync(candle, cancellationToken);
                
                OnNewCandleStick?.Invoke(candle);
                
                counter++;
                if (counter % 1000 == 0)
                {
                    var elapsed = timer.Elapsed.TotalSeconds;
                    var rate = counter / elapsed;
                    Console.WriteLine($"UltraFast: {counter} candles | {rate:F0} candles/sec");
                }
                
                // Минимальная задержка для максимальной скорости
                await Task.Yield();
            }
        }
    }
}

Пример использования в Publisher

csharp
using MassTransit;
using MassTransitFastMessages;

var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
    cfg.Host("rabbitmq://localhost", h =>
    {
        h.Username("guest");
        h.Password("guest");
    });

    cfg.ConfigureSend(s => s.UseExecute(context =>
    {
        context.Durable = false;
    }));
});

await busControl.StartAsync();

try
{
    Console.WriteLine("=== SIMPLIFIED CANDLE STICK PUBLISHER ===");
    Console.WriteLine("Choose generator:");
    Console.WriteLine("1 - Simple Generator (with validation)");
    Console.WriteLine("2 - Ultra Fast Generator (max speed)");
    
    var choice = Console.ReadLine();

    ICandleStickGenerator generator = choice == "2" 
        ? new UltraFastGenerator() 
        : new SimpleCandleStickGenerator();

    long sentCount = 0;
    var timer = System.Diagnostics.Stopwatch.StartNew();

    generator.OnNewCandleStick += async (candle) =>
    {
        try
        {
            // Быстрая отправка без лишних проверок
            await busControl.Publish(candle, context => 
            {
                context.Durable = false;
            }).ConfigureAwait(false);
            
            sentCount++;
            if (sentCount % 500 == 0)
            {
                var rate = sentCount / timer.Elapsed.TotalSeconds;
                Console.WriteLine($"Sent: {sentCount} | Rate: {rate:F0} msg/sec | {candle.Ticker}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Send error: {ex.Message}");
        }
    };

    Console.WriteLine("Generator started. Press any key to stop...");
    generator.Start();
    Console.ReadKey();
    generator.Stop();
    
    Console.WriteLine($"Total sent: {sentCount} messages");
    Console.WriteLine($"Duration: {timer.Elapsed.TotalSeconds:F2}s");
    Console.WriteLine($"Average rate: {sentCount / timer.Elapsed.TotalSeconds:F0} msg/sec");
}
finally
{
    await busControl.StopAsync();
}

Преимущества упрощенного подхода:

  1. Производительность: Меньше операций, быстрее выполнение

  2. Простота: Легче понять и поддерживать

  3. Математическая корректность: Сохраняется главное условие - все значения в пределах [L, H]

  4. Гибкость: Порядок OC/CO действительно не влияет на анализ свечи

Ваша идея абсолютно верна - в контексте свечного анализа порядок Open и Close не имеет значения, важно только их положение относительно High и Low. Это упрощение делает код более эффективным без потери функциональности!



Комментариев нет:

Отправить комментарий