🎮 Как сделать приложение на Unity + C# для тренировок по трейдингу: графики, сделки, стоп-лоссы


На главную > Блог > Категория > 🎮 Как сделать приложение на Unity + C# для тренировок по трейдингу: графики, сделки, стоп-лоссы

unity

Вступление: почему Unity — это не только игры, но и трейдинг

Вы когда-нибудь задумывались, что мощный игровой движок может быть идеальной платформой для трейдерского симулятора? Unity предлагает плавную графику (60+ FPS), богатую UI-систему и кроссплатформенность (Windows, macOS, Linux, даже мобильные устройства). А если добавить к этому C#, который трейдеры-разработчики уже знают по фреймворку .NET и WPF, порог входа становится очень низким.

Представьте: вы запускаете приложение, перед вами «живой» график, генерирующий случайные ценовые движения. Вы нажимаете «Купить», устанавливаете стоп-лосс, и приложение проверяет, сработал ли стоп при следующем движении. Это отличный способ отработать риск-менеджмент без риска потерять реальные деньги. В этой статье я покажу, как создать такой тренажёр с нуля: от генерации данных до рисования графика и обработки сделок.

«Лучший способ научиться торговать — это практика. Но лучший способ не потерять деньги на практике — это симулятор. Unity + C# дают вам оба».

1. Архитектура приложения: из чего состоит тренажёр

Наше приложение будет состоять из нескольких независимых модулей. Это упростит отладку и расширение в будущем.

Ключевые компоненты:

  • 📊 DataGenerator — генерирует случайные ценовые данные (имитация рынка).
  • 📈 ChartRenderer — рисует график (линии и, опционально, свечи).
  • 📋 OrderManager — управляет открытыми позициями (вход, стоп-лосс, закрытие).
  • 💰 AccountManager — отслеживает баланс и открытый PnL (прибыль/убыток).
  • 🎮 UIController — связывает UI-кнопки с действиями (купить, продать, установить стоп).
🎯 Важное решение: Мы не будем использовать сложные сторонние библиотеки для графиков — нарисуем их вручную через Unity UI (Image и RectTransform). Это даст полный контроль над отображением и ускорит понимание логики.

2. Настройка проекта Unity и создание UI

Шаг 1. Создаём новый проект Unity (2D или 3D — неважно, UI будет в любом)

Шаг 2. Создаём Canvas и основные элементы управления

  • 📊 Chart Panel — область, где будет отображаться график (Panel с серым фоном).
  • 💰 Balance Text — текст с текущим балансом (TextMeshPro).
  • 📈 PnL Text — текущий профит/убыток по открытым позициям.
  • 🎛️ Кнопки: Buy, Sell, Set StopLoss, Reset, New Chart.
  • 📝 Status Text — для вывода сообщений (стоп сработал, позиция открыта и т.д.).
💡 Рекомендация по верстке: Используйте Vertical Layout Group и Horizontal Layout Group для автоматического выравнивания. Это спасёт от ручной правки координат.

3. Генерация рыночных данных (рандомное блуждание)

Для имитации ценового движения используем геометрическое броуновское движение — простой и наглядный метод.


using System.Collections.Generic;
using UnityEngine;

public class DataGenerator : MonoBehaviour
{
    public int dataPoints = 100;
    public float volatility = 0.015f;   // волатильность
    public float drift = 0.001f;        // тренд (0 = случайное блуждание)
    public float startPrice = 100f;
    
    private List prices = new List();
    
    public List GenerateData()
    {
        prices.Clear();
        prices.Add(startPrice);
        
        for (int i = 1; i < dataPoints; i++)
        {
            float change = (drift + Random.Range(-volatility, volatility)) * prices[i-1];
            float newPrice = prices[i-1] + change;
            prices.Add(newPrice);
        }
        
        return prices;
    }
    
    public List GetData()
    {
        if(prices.Count == 0) GenerateData();
        return prices;
    }
}
🎲 Случайность: Используем Random.Range. В реальном симуляторе можно добавить разные режимы: тренд, флэт, высокая волатильность.

4. Отрисовка графика в Unity UI (рендеринг линии)

Вместо сложных библиотек нарисуем график вручную: создадим набор пустых объектов с Image и расставим их вдоль оси X, задавая позицию Y в зависимости от цены.


using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ChartRenderer : MonoBehaviour
{
    public DataGenerator dataGen;
    public RectTransform chartPanel;      // область графика
    public GameObject dotPrefab;          // префаб точки/кружка
    
    private List points = new List();
    private float[] cachedPrices;
    
    public void Redraw()
    {
        // Удаляем старые точки
        foreach(var p in points) Destroy(p.gameObject);
        points.Clear();
        
        List prices = dataGen.GetData();
        cachedPrices = prices.ToArray();
        
        float minPrice = Mathf.Min(prices.ToArray());
        float maxPrice = Mathf.Max(prices.ToArray());
        float priceRange = maxPrice - minPrice;
        
        float width = chartPanel.rect.width;
        float height = chartPanel.rect.height;
        
        for (int i = 0; i < prices.Count; i++)
        {
            float x = (i / (float)(prices.Count-1)) * width;
            float y = ((prices[i] - minPrice) / priceRange) * height;
            
            GameObject dot = Instantiate(dotPrefab, chartPanel);
            RectTransform rect = dot.GetComponent();
            rect.anchoredPosition = new Vector2(x, y);
            points.Add(rect);
        }
        
        // Обновляем логику для отображения цены под курсором
    }
}
🎨 Визуальное улучшение: Вместо точек можно рисовать Line Renderer или использовать Unity UI Line Renderer (пакет с GitHub). Но точек для тренировок достаточно.

5. Логика сделок: покупка, продажа и стоп-лосс

Сердце приложения — класс OrderManager, который отслеживает открытые позиции и проверяет стоп-лоссы при каждом обновлении цены.


public class OrderManager : MonoBehaviour
{
    public ChartRenderer chart;
    public AccountManager account;
    
    private float entryPrice;
    private float stopLossPrice;
    private bool hasPosition = false;
    private bool isLong = true;
    
    public void Buy(float price)
    {
        if(hasPosition)
        {
            Debug.Log("Сначала закройте текущую позицию!");
            return;
        }
        
        entryPrice = price;
        hasPosition = true;
        isLong = true;
        stopLossPrice = 0; // не установлен
        UIController.Log($"Куплено по {price:F2}");
    }
    
    public void Sell(float price)
    {
        if(hasPosition)
        {
            Debug.Log("Сначала закройте текущую позицию!");
            return;
        }
        
        entryPrice = price;
        hasPosition = true;
        isLong = false;
        stopLossPrice = 0;
        UIController.Log($"Продано по {price:F2}");
    }
    
    public void SetStopLoss(float price)
    {
        if(!hasPosition)
        {
            UIController.Log("Сначала откройте позицию!");
            return;
        }
        
        if((isLong && price < entryPrice) || (!isLong && price > entryPrice))
        {
            stopLossPrice = price;
            UIController.Log($"Стоп-лосс установлен на {price:F2}");
        }
        else
        {
            UIController.Log("Стоп-лосс должен быть в убыточной зоне!");
        }
    }
    
    public void UpdatePrice(float newPrice)
    {
        if(!hasPosition) return;
        
        // Проверка стоп-лосса
        if(stopLossPrice != 0)
        {
            if((isLong && newPrice <= stopLossPrice) || (!isLong && newPrice >= stopLossPrice))
            {
                float pnl = isLong ? (stopLossPrice - entryPrice) : (entryPrice - stopLossPrice);
                account.ApplyPnL(pnl);
                UIController.Log($"Стоп-лосс сработал! Убыток: {pnl:F2}");
                ClosePosition();
                return;
            }
        }
        
        // Обновляем плавающий PnL в UI
        float currentPnL = isLong ? (newPrice - entryPrice) : (entryPrice - newPrice);
        account.UpdateCurrentPnL(currentPnL);
    }
    
    public void ClosePosition()
    {
        if(!hasPosition) return;
        hasPosition = false;
        stopLossPrice = 0;
        account.UpdateCurrentPnL(0);
        UIController.Log("Позиция закрыта (ручное закрытие)");
    }
}

6. Управление счётом и PnL (простой баланс)


public class AccountManager : MonoBehaviour
{
    public float balance = 10000f;
    private float currentPnL = 0f;
    
    public TextMeshProUGUI balanceText;
    public TextMeshProUGUI pnlText;
    
    public void ApplyPnL(float pnl)
    {
        balance += pnl;
        UpdateUI();
    }
    
    public void UpdateCurrentPnL(float pnl)
    {
        currentPnL = pnl;
        UpdateUI();
    }
    
    public void ResetAccount()
    {
        balance = 10000f;
        currentPnL = 0f;
        UpdateUI();
    }
    
    private void UpdateUI()
    {
        balanceText.text = $"Balance: ${balance:F2}";
        pnlText.text = $"Current PnL: ${currentPnL:F2}";
        pnlText.color = currentPnL >= 0 ? Color.green : Color.red;
    }
}

7. Интерактивный UI: выбор точки входа на графике

Для максимального удобства добавим функционал: вы наводите на график — отображается цена, нажимаете — устанавливается точка входа или стоп-лосс.


public class ChartInput : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
    public ChartRenderer chart;
    public OrderManager orders;
    public RectTransform chartPanel;
    public TextMeshProUGUI priceTooltip;
    
    private bool isHovering = false;
    
    public void OnPointerClick(PointerEventData eventData)
    {
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(chartPanel, eventData.position, null, out localPoint);
        
        float price = GetPriceFromY(localPoint.y);
        
        if(Input.GetKey(KeyCode.LeftControl))  // Ctrl + клик → установить стоп-лосс
            orders.SetStopLoss(price);
        else if(Input.GetKey(KeyCode.LeftShift)) // Shift + клик → закрыть позицию
            orders.ClosePosition();
        else if(orders.HasPosition() == false)
        {
            if(price > orders.GetEntryPrice()) // пример упрощённой логики, в реальности отдельные кнопки
                orders.Buy(price);
            else
                orders.Sell(price);
        }
    }
    
    private float GetPriceFromY(float y)
    {
        float height = chartPanel.rect.height;
        float normalized = y / height;
        float minPrice = chart.GetMinPrice();
        float maxPrice = chart.GetMaxPrice();
        return minPrice + normalized * (maxPrice - minPrice);
    }
}

8. Анимация движения цены (имитация времени)

Чтобы тренажёр ощущался «живым», добавим таймер, который каждую секунду генерирует новое значение цены (как следующий бар).


public class PriceTimer : MonoBehaviour
{
    public DataGenerator dataGen;
    public ChartRenderer chart;
    public OrderManager orders;
    
    private float timer = 0f;
    private int currentIndex = 0;
    private List prices;
    
    void Start()
    {
        prices = dataGen.GenerateData();
        chart.Redraw(prices);
    }
    
    void Update()
    {
        timer += Time.deltaTime;
        if(timer >= 1f && currentIndex < prices.Count - 1)
        {
            timer = 0f;
            currentIndex++;
            float newPrice = prices[currentIndex];
            orders.UpdatePrice(newPrice);
            chart.HighlightCurrentPoint(currentIndex);
            // Обновляем дополнительный UI для отображения текущей цены
        }
        
        if(currentIndex >= prices.Count - 1)
        {
            // Торговля завершена
            UIController.Log("Данные закончились. Нажмите New Chart для перезапуска");
        }
    }
}
🎯 Расширение функционала: Добавьте ползунок скорости, чтобы ускорять или замедлять «движение рынка». Это позволит тренироваться в разных темпах.

9. Тестирование, отладка и советы по улучшению

Что проверять перед запуском:

  • ✅ При нажатии Buy/Sell баланс уменьшается на стоимость позиции (если это ваш вариант).
  • ✅ Стоп-лосс срабатывает при достижении цены (сравнение float с допуском Mathf.Approximately).
  • ✅ При закрытии позиции баланс обновляется на фиксированный PnL, а не на плавающий.
  • ✅ График перерисовывается при нажатии New Chart.
  • ✅ Нельзя установить стоп-лосс без открытой позиции.

Идеи для улучшения тренажёра:

  • 📊 Свечной график (Candlestick) — реализовать четыре параметра (High, Low, Open, Close) для каждого бара.
  • 📈 Добавление индикаторов — SMA, RSI поверх графика (как в реальных терминалах).
  • 💾 Сохранение результатов — журнал сделок для последующего анализа (SQLite или JSON).
  • 🎛️ Пользовательские настройки — выбор волатильности, начального баланса, размера лота.
  • 📜 Загрузка реальных исторических данных — чтение CSV с котировками акций/криптовалют.
🚀 Продвинутый уровень: Подключите WebSocket к бирже (Binance, Bybit) и получайте реальные котировки в реальном времени. Но начните с симуляции — это безопасно и проще для отладки логики.

Заключение: ваш личный трейдинг-тренажёр готов

Поздравляю! Вы только что создали фундамент для полноценного симулятора трейдинга на Unity. У вас есть график, система покупки/продажи, стоп-лоссы и управление балансом. Это уже больше, чем просто игрушка — это инструмент для отработки риск-менеджмента, тестирования идей и обучения без риска потерять реальные деньги.

Unity и C# оказались неожиданно мощной платформой для трейдерских приложений. Красивая графика, кроссплатформенность и знакомая экосистема .NET делают её отличным выбором как для новичков, так и для профессионалов. А главное — вы полностью контролируете каждый аспект: от рендеринга графика до обработки стоп-лоссов.

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

И последнее: не забывайте про визуальное удовольствие. Добавьте звуковые эффекты при срабатывании стоп-лосса, вибрацию (для мобильной версии), плавные анимации — сделайте так, чтобы на тренажёре хотелось тренироваться снова и снова.

«Unity — не только для игр. Это среда, где можно воплотить любую идею — от трейдерского симулятора до полноценного торгового терминала. Ваш код — ваши правила».

 

Дата размещения статьи: 2026-05-28T09:28:38