На главную > Блог > Категория > 🎮 Как сделать приложение на Unity + C# для тренировок по трейдингу: графики, сделки, стоп-лоссы
Вы когда-нибудь задумывались, что мощный игровой движок может быть идеальной платформой для трейдерского симулятора? Unity предлагает плавную графику (60+ FPS), богатую UI-систему и кроссплатформенность (Windows, macOS, Linux, даже мобильные устройства). А если добавить к этому C#, который трейдеры-разработчики уже знают по фреймворку .NET и WPF, порог входа становится очень низким.
Представьте: вы запускаете приложение, перед вами «живой» график, генерирующий случайные ценовые движения. Вы нажимаете «Купить», устанавливаете стоп-лосс, и приложение проверяет, сработал ли стоп при следующем движении. Это отличный способ отработать риск-менеджмент без риска потерять реальные деньги. В этой статье я покажу, как создать такой тренажёр с нуля: от генерации данных до рисования графика и обработки сделок.
«Лучший способ научиться торговать — это практика. Но лучший способ не потерять деньги на практике — это симулятор. Unity + C# дают вам оба».
Наше приложение будет состоять из нескольких независимых модулей. Это упростит отладку и расширение в будущем.
Для имитации ценового движения используем геометрическое броуновское движение — простой и наглядный метод.
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;
}
}
Вместо сложных библиотек нарисуем график вручную: создадим набор пустых объектов с 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);
}
// Обновляем логику для отображения цены под курсором
}
}
Сердце приложения — класс 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("Позиция закрыта (ручное закрытие)");
}
}
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;
}
}
Для максимального удобства добавим функционал: вы наводите на график — отображается цена, нажимаете — устанавливается точка входа или стоп-лосс.
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);
}
}
Чтобы тренажёр ощущался «живым», добавим таймер, который каждую секунду генерирует новое значение цены (как следующий бар).
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 для перезапуска");
}
}
}
Поздравляю! Вы только что создали фундамент для полноценного симулятора трейдинга на Unity. У вас есть график, система покупки/продажи, стоп-лоссы и управление балансом. Это уже больше, чем просто игрушка — это инструмент для отработки риск-менеджмента, тестирования идей и обучения без риска потерять реальные деньги.
Unity и C# оказались неожиданно мощной платформой для трейдерских приложений. Красивая графика, кроссплатформенность и знакомая экосистема .NET делают её отличным выбором как для новичков, так и для профессионалов. А главное — вы полностью контролируете каждый аспект: от рендеринга графика до обработки стоп-лоссов.
Дальше — дело за вами. Добавляйте свечи, индикаторы, журналы сделок, режимы рынка. Превратите этот тренажёр в полноценную обучающую платформу, которой можно гордиться. И помните: даже лучший симулятор не заменит дисциплины и риск-менеджмента в реальной торговле. Но он поможет вам приобрести эти навыки без потери депозита.
И последнее: не забывайте про визуальное удовольствие. Добавьте звуковые эффекты при срабатывании стоп-лосса, вибрацию (для мобильной версии), плавные анимации — сделайте так, чтобы на тренажёре хотелось тренироваться снова и снова.
«Unity — не только для игр. Это среда, где можно воплотить любую идею — от трейдерского симулятора до полноценного торгового терминала. Ваш код — ваши правила».
Дата размещения статьи: 2026-05-28T09:28:38