среда, 17 сентября 2025 г.

MathLib, ChartDirector, Projects

F:\Projects\VS\RazorzzSln\RazorzzSln.sln

F:\Projects\VS\RazorzzSln\MathLib\MathLib.csproj

-----------------------------------------------------------------------------

// 1) Генерация оси X (аналог linspace)

// Включительно: start и end входят в результат.

public static double[] Linspace(double start, double end, int count)

-------------------------------------------------------------------------------------------------

public static double[] Linspace(double start, double end, int count, int startFromPosition)

--------------------------------------------------------------------------------------------------

public static double[] LinspacePingPong(double start, double end, int count, int startFromPosition)

--------------------------------------------------------------------------------------------------

// Вариант со step и включительным endControl: если step > 0 — идём вверх, если < 0 — вниз.

// End может быть не достигнут ровно, берём все точки, не проходя за end по знаку step.

public static double[] Linspace(double start, double step, double end)

-----------------------------------------------------------------------------------------------------

// Удобный вариант: количество и полуинтервал [start, end)

// Возвращает count точек с фиксированным шагом, end не включается.

public static double[] LinspaceHalfOpen(double start, double end, int count)

-------------------------------------------------------------------------------------------------------

// Chad

// Первая функция: копирование по кольцу входного массива, начиная с StartIndex

// и двигаясь вперёд, оборачиваясь к началу по мере необходимости.

// Результирующий массив имеет ту же длину, полностью заполнен.

public static T[] CopyFromMidToWrap<T>(T[] input, int startIndex)

--------------------------------------------------------------------------------------------------

// Вторая функция: копирование по кольцу входного массива, но двигаемся в обратном

// направлении после достижения конца массива. По сути, идём вокруг в обратном порядке.

// При этом стартовая позиция задаёт начальный индекс в входном массиве.

public static T[] CopyFromMidToWrapReverse<T>(T[] input, int startIndex)

------------------------------------------------------------------------------------------------------


ChartDirector, RazorzzSln, Projects

ChartDirector, RazorzzSln, Projects

F:\Projects\VS\RazorzzSln\RazorzzSln.sln

F:\Projects\VS\RazorzzSln\RazorCandle01\RazorCandle01.csproj
CandleStick, Static, Merc

--------------------------------------------------------------------------------------------

F:\Projects\VS\RazorzzSln\RazorCandle02\RazorCandle02.csproj

Key Project, WW

CandleStick, Dynamic, NoMerc, Ajax,

---------------------------------------------------------------------------------------------

F:\Projects\VS\RazorzzSln\RazorSine03\RazorSine03.csproj

Sine, Dynamic, Merc, Meta(Html)

---------------------------------------------------------------------------------------------

F:\Projects\VS\RazorzzSln\RazorSine03\RazorSine04.csproj

F:\Projects\VS\RazorzzSln\RazorSine05\RazorSine05.csproj

Key Project, WW

Sine, Dynamic, NoMerc, Ajax,

-----------------------------------------------------------------------------------------------

F:\Projects\VS\RazorzzSln\Razorzz01\Razorzz01.csproj

SimpleLine, Static, OneTime

------------------------------------------------------------------------------------------------

F:\Projects\VS\RazorzzSln\Razorzz02\Razorzz02.csproj

Sine, Dynamic, Merc, Timeout

-------------------------------------------------------------------------------------------------

F:\Projects\VS\RazorzzSln\MathLib\MathLib.csproj

--------------------------------------------------------------------------------------------------

вторник, 9 сентября 2025 г.

ChartDirector, Project, ToDo

ChartDirector, Project, Giga, ToDo

ChartDirector Finance Porjects C#

Для реализации проекта ChartDirector на платформе .NET (C#) вам потребуется библиотека ChartDirector, которая позволяет создавать разнообразные графики и диаграммы высокого качества прямо внутри вашего приложения. Вот пошаговая инструкция по созданию простого графического представления финансовых данных с использованием ChartDirector и C#.

Шаг 1: Установка библиотеки

1.   Скачайте библиотеку ChartDirector для Windows (.NET). Это платная библиотека, однако доступна бесплатная пробная версия.

2.   После установки добавьте ссылку на библиотеку (NChartDir.dll) в ваш проект Visual Studio.Откройте Solution Explorer → щелкните правой кнопкой мыши на проекте → выберите Add Reference → перейдите на вкладку Browse и найдите файл NChartDir.dll.

Шаг 2: Создание графика

Создаем простое приложение WPF или WinForms, где будем отображать график изменения акций или валюты за определенный период.

Пример кода для визуализации линейного графика цен акций:

using System.Windows.Forms;

using ChartDirector;

 

public class FinancialGraph : Form

{

    private PictureBox pictureBox = new PictureBox();

   

    public FinancialGraph()

    {

        // Настройка формы

        this.Text = "Financial Graph";

        this.Width = 800;

        this.Height = 600;

       

        // Создаем PictureBox для отображения графика

        pictureBox.Dock = DockStyle.Fill;

        Controls.Add(pictureBox);

       

        DrawChart();

    }

 

    private void DrawChart()

    {

        // Данные акции (пример)

        double[] prices = { 100, 102, 105, 103, 107 };

        string[] labels = { "Jan", "Feb", "Mar", "Apr", "May" };

 

        XYChart chart = new XYChart(600, 400); // Размер графика

       

        // Добавляем названия осей

        chart.addTitle("Акции Компании XYZ");

        chart.xAxis().setTitle("Месяцы");

        chart.yAxis().setTitle("Цена ($)");

 

        // Устанавливаем значения на оси X

        chart.xAxis().setLabels(labels);

 

        // Рисуем линию графика

        LineLayer lineLayer = chart.addLineLayer(prices, 0xff0000, "Цены акций"); // Цвет линии красный

 

        // Генерируем картинку

        Bitmap bitmap = chart.makeBitmap();

       

        // Отображаем графику в PictureBox

        pictureBox.Image = bitmap;

    }

}

 

// Запуск программы

static class Program

{

    static void Main()

    {

        Application.EnableVisualStyles();

        Application.Run(new FinancialGraph());

    }

}

Что сделано в коде?

1.   Мы создали форму с элементом PictureBox, куда будем выводить готовый график.

2.   Использовали класс XYChart из библиотеки ChartDirector для рисования двухосевого графика.

3.   Через метод addLineLayer() добавили слой линий для наших значений цен акций.

4.   Задали метки на оси Х и заголовки для осей.

5.   Наконец, преобразовали объект chart в растровый рисунок и показали его в форме.

Возможности расширения

Библиотека ChartDirector поддерживает огромное количество типов графиков и диаграмм: гистограммы, круговые диаграммы, поверхности, пузырьковые графики и многое другое. Вы можете легко расширить этот пример, добавив интерактивность (например, вывод всплывающих подсказок при наведении мышью).

Таким образом, ChartDirector — мощный инструмент для построения профессиональных графиков на платформе .NET/C#, позволяющий эффективно представить финансовые данные вашему приложению.

ТЫ все правильно понял.Давай посмотрим Пример        private void createChart(RazorChartViewer viewer)        {            // Create a finance chart demo containing 100 days of data            int noOfDays = 100;

        // To compute moving averages starting from the first day, we need to get extra data

        // points before the first day

        int extraDays = 30;

 

        // In this exammple, we use a random number generator utility to simulate the data. We

        // set up the random table to create 6 cols x (noOfDays + extraDays) rows, using 9 as

        // the seed.

        RanTable rantable = new RanTable(9, 6, noOfDays + extraDays);

 

        // Set the 1st col to be the timeStamp, starting from Sep 4, 2002, with each row

        // representing one day, and counting week days only (jump over Sat and Sun)

        rantable.setDateCol(0, new DateTime(2002, 9, 4), 86400, true);

 

        // Set the 2nd, 3rd, 4th and 5th columns to be high, low, open and close data. The open

        // value starts from 100, and the daily change is random from -5 to 5.

        rantable.setHLOCCols(1, 100, -5, 5);

 

        // Set the 6th column as the vol data from 5 to 25 million

        rantable.setCol(5, 50000000, 250000000);

 

        // Now we read the data from the table into arrays

        double[] timeStamps = rantable.getCol(0);

        double[] highData = rantable.getCol(1);

        double[] lowData = rantable.getCol(2);

        double[] openData = rantable.getCol(3);

        double[] closeData = rantable.getCol(4);

        double[] volData = rantable.getCol(5);

 

        // Create a FinanceChart object of width 640 pixels

        FinanceChart c = new FinanceChart(640);

 

        // Add a title to the chart

        c.addTitle("Finance Chart Demonstration");

 

        // Set the data into the finance chart object

        c.setData(timeStamps, highData, lowData, openData, closeData, volData, extraDays);

 

        // Add the main chart with 240 pixels in height

        c.addMainChart(240);

 

        // Add a 5 period simple moving average to the main chart, using brown color

        c.addSimpleMovingAvg(5, 0x663300);

 

        // Add a 20 period simple moving average to the main chart, using purple color

        c.addSimpleMovingAvg(20, 0x9900ff);

 

        // Add HLOC symbols to the main chart, using green/red for up/down days

        c.addHLOC(0x008000, 0xcc0000);

 

        // Add 20 days bollinger band to the main chart, using light blue (9999ff) as the border

        // and semi-transparent blue (c06666ff) as the fill color

        c.addBollingerBand(20, 2, 0x9999ff, unchecked((int)0xc06666ff));

 

        // Add a 75 pixels volume bars sub-chart to the bottom of the main chart, using

        // green/red/grey for up/down/flat days

        c.addVolBars(75, 0x99ff99, 0xff9999, 0x808080);

 

        // Append a 14-days RSI indicator chart (75 pixels high) after the main chart. The main

        // RSI line is purple (800080). Set threshold region to +/- 20 (that is, RSI = 50 +/-

        // 25). The upper/lower threshold regions will be filled with red (ff0000)/blue

        // (0000ff).

        c.addRSI(75, 14, 0x800080, 20, 0xff0000, 0x0000ff);

 

        // Append a 12-days momentum indicator chart (75 pixels high) using blue (0000ff) color.

        c.addMomentum(75, 12, 0x0000ff);

 

        // Output the chart

        viewer.Image = c.makeWebImage(Chart.SVG);

    }


Что мы здесь видим.Для демонстрации нормально.Но для реального времени надо это исправлять.Итак реальное время. Сильно упрощенно.Здесь сразу выполняется Генерация 100 + 20 свечей и потом выводится весь массив загружается в график.В реальном времени все по-другому.В единицу времени к нам прилетает одна свеча.Далее эта свеча добавляется к текущему массиву свечей и этот массив выводится в Chart.То есть генератор должен генерить одну свечу , добавлять в массив первой, убирать последнюю свечю и выводить весь массив свечей в График. Таким образом у нас кадр предыдущий от кадра текущего отличается одной новой свечой и убранной последней свечой. Сюда настоятельно просится CircularBuffer.Начальный моментВот в Начальный момент, например, в начале торгов должны загружаться свечи, кол-во корых должно закрывать весь график, а потом переходим к текущему моменту, описанному выше.То есть Здесь надо запрограммировать Начальный момент, где работает Random генератор, иуже Текущую торговлю с добавлением первой свечи (самую левую) и убиранием последней свечи.Получается,что генератор свеч нужен в Начальный момент для генерации Массива свеч на весь графики далее в текущей торговле Генератор нужен для генерации одной свечи.Как думаешь Генераторы , которые представлены в этом Примере подойдут для выполнения двух кейсов: 1. Генерация массива свечей, Генерация одной свечи.В Текущей торговле мы все же можем использовать CircularBuffer обязательно с очередью и с массивом уже готовых свечей, которые в каждый интервал будут переписываться в буфера для рендинга (здесь надо применить механизмы синхронизации).

Как тебе такая схема ? Выскажи свое мнение на словах. Пока без кода.

Ответ

Предложенная тобой схема выглядит вполне разумной и соответствует общим принципам эффективной обработки потоковых данных в режиме реального времени. Давай разберём её подробнее:

1. Начальная загрузка (Генерация массива свечей):

·         Сначала генерируется начальный массив свечей, достаточный для заполнения всего графика. Для этого отлично подойдёт случайный генератор (Random Generator), представленный в примере. Его задача проста: создать заранее заданное число свечей для начальной загрузки графика.

·         Если речь идёт о демо-версии или симуляции исторических данных, такой подход абсолютно оправдан.

2. Обработка новых данных (Режим реальной торговли):

·         По мере поступления новых данных (каждая новая свеча) старые данные постепенно отбрасываются, обеспечивая постоянное обновление экрана. Ты верно заметил, что тут идеально подходит структура типа кольцевого буфера (Circular Buffer), так как она позволяет быстро обновлять данные, поддерживая постоянный размер массива.

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

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

Синхронизация:

·         Важно учитывать возможные проблемы параллелизма, особенно если обновления происходят одновременно с процессом рендера. Нужно предусмотреть меры синхронизации (локи, семафоры и т.п.) для предотвращения некорректного чтения или записи данных в моменты переключения кадров.

Механизм рендеринга:

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

Заключение:

Идея использовать разные генераторы для начальных и текущих данных верна и эффективна. Так как требования к генераторам различны: первый предназначен для быстрой генерации большого объёма данных, второй – для точной обработки отдельных поступающих элементов.

Общий подход выглядит следующим образом:

·         Начальные данные: Random Generator создаёт полный набор данных для первоначального наполнения графика.

·         Реальное время: Кольцевой буфер хранит ограниченное количество последних свечей, постоянно дополняясь новыми данными и избавляясь от устаревших.

Эта архитектура позволит достичь плавного и эффективного отображения динамики данных в реальном времени, сохраняя хорошую производительность даже при больших объёмах данных.

В одну секунду необходимоГенерить Новую свечу, Убрать старую.Закинуть Новую свечу в CircularBuffer (CB), Самая старая свеча сама исчезнет из CBВыгрузить из CB массив свечей и загрузить этот массив в массив для Рендинга.Если возможно брать свечи для рендинга прямо из CB, то надо это сделать.Также надо подумать как мы будем хранить свечи в CB  в 6-сти массивах (DT, H,L,O, C, Volume)или хранить свечи  будем в виде class CandleStick полями, а для рендинга будем разбрасывать CandleStick на шесть массивов - так как это нужно для рендинга.Так как задача вполне подъемная, может быть надо будет использовать BackGroundService.В BackGroundServict мы вычлсляем каждый кадр, заполняем Буфера для рендинга.А Рендинг будет запускаться через каждую секунду. У него задача простая - создать каринку - график из шести массивов и нарисовать его в Browser.Вот такая задача.

1.   Выскажи свое мнение.

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

Ответ

Задача действительно интересная и требует тщательного подхода к организации потоков данных и синхронизации процессов. Попробую сформулировать проект более чётко и понятно, основываясь на твоих комментариях.


Проект: Реалтайм график с обработкой свечей в кольцевом буфере

Задача:

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

Подход:

Система состоит из двух основных компонентов:

1.   Background Service, который занимается расчётом новых данных и обновляет внутренний кольцевой буфер.

2.   Rendering Component, который каждые секунды берёт актуальные данные из кольцевого буфера и производит рендеринг нового состояния графика.


Компоненты системы

1. Background Service (обновление данных)

Этот компонент обрабатывает входящие данные (котировки, объемы и т.д.). Каждые n секунд (например, каждую секунду) он вычисляет новую свечу и вставляет её в начало кольцевого буфера (Circular Buffer). Одновременно самая старая свеча выбрасывается из буфера, так как буфер имеет фиксированный размер.

Структура данных в буфере:

Каждый элемент буфера представляет собой структуру типа Candlestick. Она включает поля:

·         Timestamp (dt),

·         High price (h),

·         Low price (l),

·         Open price (o),

·         Close price (c),

·         Volume (v).

Структуру можно реализовать так:class Candlestick{    public DateTime dt;    public double h;    public double l;    public double o;    public double c;    public long v;}

Буфер представляет собой коллекцию объектов типа Candlestick, организованных в кольцеобразной структуре. Длина буфера выбирается исходя из количества свечей, необходимых для полного покрытия окна графика.

Процесс добавления данных:

Каждую секунду сервис вычисляет новую свечу и помещает её в начало буфера, смещая остальные элементы вперёд. Последняя свеча вытесняется и теряется.

Пример метода для добавления новой свечи:void UpdateCandle(CircularBuffer<Candlestick> buffer, Candlestick newCandle){    if (!buffer.IsFull())    {        buffer.Enqueue(newCandle); // Добавляем в конец    }    else    {        buffer.Dequeue();          // Удаляем первую (самую старую)        buffer.Enqueue(newCandle); // Добавляем новую    }}

2. Rendering Component (визуализация графика)

Этот компонент отвечает за создание графика и его регулярное обновление. Каждые N миллисекунд (обычно ~1000 мс) он извлекает содержимое кольцевого буфера и отправляет его на рендеринг.

График строится с использованием данных непосредственно из кольцевого буфера. Важно обеспечить согласованность операций чтения и записи данных, используя примитивы синхронизации (например, lock или семафоры).

Архитектура взаимодействия компонентов:

1.   Данные поступают в background service. Каждая секунда формируется новая свеча.

2.   Новая свеча записывается в кольцевой буфер (одновременно удаляя самую старую свечу).

3.   Renderer периодически запрашивает состояние буфера и строит новый кадр графика.

4.   Готовая картинка отправляется в браузер для отображения.


Детали реализации:

1. Circular Buffer:

Реализуется стандартным кольцевым буфером с поддержкой фиксированного размера. Ключевая операция – последовательное смещение данных.

Пример структуры:class CircularBuffer<T>{    private readonly T[] _data;    private int _head, _tail, _count;    public bool IsFull => _count >= _data.Length;    public CircularBuffer(int capacity)    {        _data = new T[capacity];    }    public void Enqueue(T item)    {        lock (_data)        {            if (_count == _data.Length)                Dequeue(); // Автоматически удаляем старый элемент перед добавлением нового            _data[_tail++] = item;            _tail %= _data.Length;            ++_count;        }    }    public T Dequeue()    {        lock (_data)        {            var result = _data[_head++];            _head %= _data.Length;            --_count;            return result;        }    }}

2. Хранение данных:

Свечи хранятся в виде объектов класса Candlestick. В каждом таком объекте содержится вся необходимая информация для отображения свечи на графике.

При рендеринге графа отдельные массивы для каждого параметра (high, low, open, close, volume) формируются динамически на основе содержимого буфера.

Например, для рендеринга достаточно вызвать такую функцию:(double[], double[], double[], double[], double[]) GetArraysForRender(CircularBuffer<Candlestick> buffer){    List<double> highs = new(), lows = new(), opens = new(), closes = new(), volumes = new();    foreach(var candle in buffer.ToArray())    {        highs.Add(candle.h);        lows.Add(candle.l);        opens.Add(candle.o);        closes.Add(candle.c);        volumes.Add(candle.v);    }    return (highs.ToArray(), lows.ToArray(), opens.ToArray(), closes.ToArray(), volumes.ToArray());}

3. Рендеринг графика:

Используя библиотеку вроде ChartDirector, можно организовать рендеринг на основе полученных массивов данных. Библиотека позволяет строить широкий спектр графиков и диаграмм, включая свечные графики (OHLC).

Пример рендеринга OHLC-графика:private void RenderChart(RazorChartViewer viewer, CircularBuffer<Candlestick> buffer){    // Получаем массивы для рендеринга    var (highs, lows, opens, closes, _) = GetArraysForRender(buffer);    // Время берётся из массива timestamp'ов    double[] timestamps = buffer.Select(x => x.dt.Ticks / TimeSpan.TicksPerSecond).ToArray();    // Строим график    XYChart chart = new XYChart(640, 400);    chart.addTitle("Real-time Stock Price");    chart.xAxis().setTitle("Time");    chart.yAxis().setTitle("Price");    // Дочерний график OHLC    chart.addCandleStick(highs, lows, opens, closes, 0x008000, 0xcc0000);    // Выводим SVG-изображение    viewer.Image = chart.makeWebImage(Chart.SVG);}

Итоговая схема:

1.   Пользователь открывает страницу.

2.   Браузер запрашивает у сервера первое заполнение графика (начальные данные).

3.   Сервер возвращает исходные данные (инициализация графика).

4.   Background Service регулярно получает свежие данные и обновляет кольцевой буфер.

5.   Renderer каждые N секунд читает данные из буфера и отправляет готовую картинку обратно браузеру.


Оптимизации и улучшения:

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

·         Организация кеширования промежуточных результатов для ускорения последующих рендеров.

·         Повышение производительности рендеринга путём оптимизации библиотеки ChartDirector (например, настройка кэш-полей, выбор наиболее производительных режимов рендеринга).


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

Еще одна важная деталь по BackgroundServiceКроме описанных выше функций он еще будет делать следущее:

Этот Сервис будет работать в нескольких режимах.1A.

·         Генерация quotes(Ticker, DT, Bid, ASK, Last, VolumeInLast   - Random

·         Построение Candle по сгенеренным quotes1B.Генерация Candle по RandomGenerator

2A.

·         Получение quotes(Ticker, DT, Bid, ASK, Last, VolumeInLast   из Внешних источников (MAssTRanzit)

·         Построение Candle по сгенеренным quotes2B.Получение:  Candle из Внешних источников (MAssTRanzit)Вот такой у нас должен быт функционал.

Оцените мысль.Подрихтуйте этот текст и добавьте его к Описанию BackGroundService

Ответ

Извините, произошла ошибка

Еще одна важная деталь по BackgroundServiceКроме описанных выше функций он еще будет делать следущее:

Этот Сервис будет работать в нескольких режимах.1A.

Генерация quotes(Ticker, DT, Bid, ASK, Last, VolumeInLast   - Random

Построение Candle по сгенеренным quotes1B.Генерация Candle по RandomGenerator

2A.

Получение quotes(Ticker, DT, Bid, ASK, Last, VolumeInLast   из Внешних источников (MAssTRanzit)

Построение Candle по сгенеренным quotes2B.Получение:  Candle из Внешних источников (MAssTRanzit)Вот такой у нас должен быт функционал.

Оцените мысль.Подрихтуйте этот текст и добавьте его к Описанию BackGroundService