Зачем симулировать токеномику
Табличная модель токеномики — это один сценарий: «при таких-то параметрах будет такой-то результат». Но параметры всегда неточны. Сколько пользователей придёт — 5 000 или 50 000? Какой процент застейкает? Когда инвесторы начнут продавать?
Симуляция отвечает не на вопрос «что будет», а на вопрос «что может быть и с какой вероятностью». Вместо одного прогноза — распределение исходов. Вместо «цена будет $0.50» — «в 90% сценариев цена остаётся выше $0.30».
В этой статье — четыре уровня симуляций, от простых к сложным. Каждый следующий уровень добавляет точность, но и требует больше данных и кода.
Уровень 1: Анализ чувствительности
Самый простой метод. Берём табличную модель и меняем один параметр, фиксируя остальные. Смотрим, как результат зависит от этого параметра.
Когда применять
- На ранней стадии проектирования — чтобы понять, какие параметры критичны
- При подборе параметров bonding curve, эмиссии, вестинга
- Для ответа на вопрос «что сломается первым»
Пример: чувствительность стейкинга к APR
Протокол с эмиссией 800 токенов/день. Вопрос: при каком проценте стейкинга APR становится ниже 5% (порог, при котором крупные стейкеры уходят)?
Результат: при стейкинге выше ~58% APR падает ниже 5%. Это критический порог — если модель предполагает 60% стейкинга, система работает на грани.
| Процент стейкинга | APR | Статус |
|---|---|---|
| 20% | 14.6% | Безопасно |
| 40% | 7.3% | Приемлемо |
| 58% | 5.0% | Порог |
| 60% | 4.9% | Ниже порога |
| 80% | 3.7% | Критично |
Код симуляции (Python)
import numpy as np
import matplotlib.pyplot as plt
total_supply = 10_000_000
daily_rewards = 800
staking_pcts = np.linspace(0.1, 0.9, 50) # от 10% до 90% стейкинга
aprs = (daily_rewards * 365) / (total_supply * staking_pcts)
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(staking_pcts * 100, aprs * 100, linewidth=2.5, color='#2563eb')
ax.axhline(y=5, color='#dc2626', linestyle='--', label='Порог 5% APR')
ax.fill_between(staking_pcts * 100, aprs * 100, 5,
where=(aprs * 100 < 5), alpha=0.15, color='#dc2626')
ax.set_xlabel('Процент стейкинга (%)')
ax.set_ylabel('APR (%)')
ax.set_title('Чувствительность APR к проценту стейкинга')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Уровень 2: Сценарный анализ
Фиксируем набор параметров для каждого сценария. Стандартный подход — три сценария:
| Параметр | Пессимистичный | Базовый | Оптимистичный |
|---|---|---|---|
| Пользователи (мес 12) | 5 000 | 20 000 | 80 000 |
| Процент стейкинга | 30% | 50% | 70% |
| Отток | 15%/мес | 8%/мес | 3%/мес |
| Давление на продажу (инвесторы) | 80% после клиффа | 50% | 20% |
Что показывает
В пессимистичном сценарии давление на продажу составляет 1.6M токенов/мес (80% от 2M разлока), а свободный float растёт быстрее (мало стейкеров). Это двойной удар по цене.
В оптимистичном — продаётся лишь 400K/мес, а 70% стейкается. Давление на продажу в 4 раза ниже.
| Метрика | Пессимистичный | Базовый | Оптимистичный |
|---|---|---|---|
| Давление на продажу | 1.6M/мес | 1.0M/мес | 0.4M/мес |
| Свободный float (мес 12) | 23.8M | 17.0M | 10.2M |
| Соотношение sell / float | 6.7% | 5.9% | 3.9% |
Код симуляции (Python)
import numpy as np
import matplotlib.pyplot as plt
months = np.arange(1, 25)
scenarios = {
'Пессимистичный': {
'users_final': 5_000,
'stake_pct': 0.30,
'sell_pressure': 0.80,
'color': '#dc2626'
},
'Базовый': {
'users_final': 20_000,
'stake_pct': 0.50,
'sell_pressure': 0.50,
'color': '#2563eb'
},
'Оптимистичный': {
'users_final': 80_000,
'stake_pct': 0.70,
'sell_pressure': 0.20,
'color': '#16a34a'
}
}
total_supply = 100_000_000
initial_circulating = 10_000_000 # TGE
monthly_unlock = 2_000_000 # вестинг инвесторов
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for name, s in scenarios.items():
circulating = np.zeros(len(months))
net_sell = np.zeros(len(months))
for i, m in enumerate(months):
unlocked = min(initial_circulating + monthly_unlock * m, total_supply)
staked = unlocked * s['stake_pct']
free_float = unlocked - staked
sell_tokens = monthly_unlock * s['sell_pressure']
circulating[i] = free_float
net_sell[i] = sell_tokens
axes[0].plot(months, circulating / 1e6, label=name,
color=s['color'], linewidth=2)
axes[1].plot(months, net_sell / 1e6, label=name,
color=s['color'], linewidth=2)
axes[0].set_title('Свободный float (млн токенов)')
axes[0].set_xlabel('Месяц')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].set_title('Давление на продажу (млн токенов/мес)')
axes[1].set_xlabel('Месяц')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Уровень 3: Монте-Карло
Метод Монте-Карло — это тысячи случайных сценариев. Вместо фиксированных параметров задаём распределения: «пользователей будет от 5 000 до 80 000, наиболее вероятно ~20 000». Модель запускается 1 000–10 000 раз с разными случайными значениями.
Результат — не точка и не три точки, а полное распределение исходов с перцентилями и доверительными интервалами.
Как работает
- Определяем входные параметры и их распределения
- На каждой итерации сэмплируем значения из распределений
- Прогоняем модель, записываем результат
- Повторяем 1 000–10 000 раз
- Анализируем распределение результатов
Выбор распределений
| Параметр | Распределение | Почему |
|---|---|---|
| Число пользователей | Логнормальное | Рост может быть взрывным, но не отрицательным |
| Процент стейкинга | Бета | Ограничен от 0 до 1, можно задать моду |
| Давление на продажу | Бета | Аналогично — доля от 0 до 1 |
| Время до продажи | Экспоненциальное | Большинство продаёт быстро, единицы ждут долго |
| Цена токена | Логнормальное | Мультипликативная динамика, не бывает отрицательной |
Пример: устойчивость казначейства
Протокол поднял $5M на TGE. Команда тратит деньги, но зарабатывает комиссию с пользователей. Вопрос: хватит ли казначейства на 36 месяцев?
Модель состоит из четырёх параметров. Каждый сэмплируется из распределения, потому что точное значение неизвестно заранее:
| Параметр | Распределение | Диапазон | Почему именно такой |
|---|---|---|---|
| Ежемесячный burn rate | Логнормальное (медиана $150K, σ=0.3) | $90K – $250K | Зарплаты, инфраструктура, маркетинг. Логнормальное — потому что расходы не бывают отрицательными, но могут неожиданно вырасти |
| Рост пользователей | Нормальное (μ=8%, σ=4%) | 0% – 16%/мес | Органический рост с высокой неопределённостью. Может быть близок к нулю |
| Доход на пользователя | Равномерное ($2 – $8/мес) | $2 – $8 | Комиссии протокола. Диапазон на основе аналогов (DeFi: $3–$5, GameFi: $1–$2) |
| Начальные пользователи | Логнормальное (медиана 2000, σ=0.5) | 800 – 5 000 | Зависит от успеха маркетинговой кампании на TGE |
Зависимости внутри модели:
- Доход = пользователи × доход_на_пользователя (больше пользователей → больше дохода)
- Burn rate растёт на 2%/мес (инфляция зарплат, рост команды)
- Баланс казначейства = предыдущий баланс + доход − расходы
- Если баланс падает до 0 — протокол не может оплачивать операции
Результат 2 000 прогонов:
| Метрика | Значение |
|---|---|
| Медиана (P50) баланса на месяц 24 | ~$3.2M |
| 5-й перцентиль (P5) на месяц 24 | ~$0.4M |
| 95-й перцентиль (P95) на месяц 24 | ~$8.1M |
| Доля прогонов с обнулением к месяцу 36 | ~12% |
Ключевой вывод: в 12% прогонов казначейство обнуляется до месяца 36. Это значит, что при текущих параметрах протокол имеет ~88% шансов дожить до трёх лет без дополнительного фандрейзинга. Если порог приемлемого риска — 5%, нужно либо снижать burn rate, либо увеличивать начальное казначейство до ~$7M.
Код симуляции (Python)
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
n_simulations = 2000
n_months = 36
initial_treasury = 5_000_000 # $5M
results = np.zeros((n_simulations, n_months))
for sim in range(n_simulations):
treasury = initial_treasury
# Сэмплируем параметры для этого прогона
monthly_burn = np.random.lognormal(mean=np.log(150_000), sigma=0.3)
user_growth = np.random.normal(0.08, 0.04) # 8% ± 4% рост/мес
revenue_per_user = np.random.uniform(2, 8) # $/user/мес
initial_users = np.random.lognormal(mean=np.log(2000), sigma=0.5)
users = initial_users
for month in range(n_months):
users *= (1 + max(user_growth + np.random.normal(0, 0.02), -0.1))
revenue = users * revenue_per_user
burn = monthly_burn * (1 + 0.02 * month) # расходы растут 2%/мес
treasury = treasury + revenue - burn
results[sim, month] = max(treasury, 0)
# === Визуализация ===
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
months = np.arange(1, n_months + 1)
p5 = np.percentile(results, 5, axis=0)
p25 = np.percentile(results, 25, axis=0)
p50 = np.percentile(results, 50, axis=0)
p75 = np.percentile(results, 75, axis=0)
p95 = np.percentile(results, 95, axis=0)
axes[0].fill_between(months, p5/1e6, p95/1e6, alpha=0.1, color='#2563eb')
axes[0].fill_between(months, p25/1e6, p75/1e6, alpha=0.2, color='#2563eb')
axes[0].plot(months, p50/1e6, color='#2563eb', linewidth=2, label='Медиана')
axes[0].plot(months, p5/1e6, color='#dc2626', linewidth=1,
linestyle='--', label='5-й перцентиль')
axes[0].axhline(y=0, color='black', linewidth=0.5)
axes[0].set_xlabel('Месяц')
axes[0].set_ylabel('Казначейство ($M)')
axes[0].set_title('Монте-Карло: баланс казначейства')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
bankrupt_by_month = np.zeros(n_months)
for month in range(n_months):
bankrupt_by_month[month] = np.mean(results[:, month] == 0) * 100
axes[1].bar(months, bankrupt_by_month, color='#dc2626', alpha=0.7)
axes[1].set_xlabel('Месяц')
axes[1].set_ylabel('% прогонов с пустым казначейством')
axes[1].set_title('Кумулятивная вероятность банкротства')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Как читать результаты
| Метрика | Что показывает | Пример |
|---|---|---|
| Медиана (P50) | Наиболее вероятный исход | Казначейство = $3.2M через 24 мес |
| 5-й перцентиль (P5) | Худший реалистичный сценарий | Казначейство = $0.4M через 24 мес |
| 95-й перцентиль (P95) | Лучший реалистичный сценарий | Казначейство = $8.1M через 24 мес |
| Вероятность события | Шанс конкретного исхода | 12% прогонов: казначейство = 0 к месяцу 36 |
| VaR (Value at Risk) | Максимальная потеря с заданной вероятностью | С вероятностью 95% потери не превысят $4.6M |
Типичные ошибки
- Коррелированные параметры сэмплируются независимо. Если растёт число пользователей — растёт и нагрузка на казначейство. Используйте копулы или совместные распределения
- Слишком широкие распределения. «Пользователей от 100 до 10 000 000» — не информативно. Сужайте диапазоны на основе аналогов
- Мало итераций. 100 прогонов не дадут стабильных перцентилей. Минимум 1 000, лучше 5 000
- Игнорирование хвостов. Средний результат неинтересен — важны экстремальные сценарии
Уровень 4: Агентное моделирование
Монте-Карло варьирует параметры, но модель внутри каждого прогона остаётся детерминированной: формулы считают от начала до конца. Агентное моделирование (ABM) добавляет ещё один слой — поведение отдельных участников.
В ABM каждый пользователь — отдельный агент со своим балансом, стратегией и правилами принятия решений. На каждом шаге агенты реагируют на текущее состояние системы, и их действия меняют это состояние для всех остальных.
Это позволяет моделировать эмерджентные эффекты: каскадные выходы из стейкинга, атаки на governance, bank-run на пулы ликвидности — всё то, что невозможно выразить формулой.
Подробный разбор с кодом, примером стейкинга и банк-рана — в отдельной статье: Агентное моделирование в токеномике.
Сравнение методов
| Анализ чувствительности | Сценарный | Монте-Карло | ABM | |
|---|---|---|---|---|
| Сложность | Низкая | Низкая | Средняя | Высокая |
| Число исходов | N точек | 3–5 | 1 000+ | 1 000+ |
| Взаимосвязи параметров | Нет | Вручную | Через распределения | Через поведение |
| Эмерджентные эффекты | Нет | Нет | Нет | Да |
| Инструменты | Google Sheets | Google Sheets | Python / R | Python (cadCAD, radCAD) |
| Когда применять | Ранний этап | Презентация инвесторам | Стресс-тест модели | Сложные механизмы |
Что использовать на каком этапе
- Анализ чувствительности — определите, какие параметры критичны. Это занимает час в таблице
- Сценарный анализ — покажите 3 сценария инвесторам и команде. Общий язык для принятия решений
- Монте-Карло — проверьте устойчивость: «в каком проценте прогонов всё ломается?». Нужен Python
- Агентное моделирование — если в системе есть участники с конкурирующими стратегиями и обратная связь между действиями
Комбинирование методов
На практике методы не исключают друг друга. Типичный порядок для проекта:
- Таблица — allocation, вестинг, базовая unit-экономика
- Анализ чувствительности — находим параметры, к которым модель наиболее чувствительна
- Монте-Карло — 2 000 прогонов по ключевым параметрам, получаем перцентили
- ABM — для критических механизмов (стейкинг, AMM, governance) строим агентную модель и запускаем 100+ прогонов
Каждый шаг информирует следующий: анализ чувствительности показывает, что варьировать в Монте-Карло. Монте-Карло показывает, где нужна ABM.
Практические рекомендации
Когда хватит таблицы
- Allocation и вестинг — фиксированные графики, нечего симулировать
- Unit-экономика на ранней стадии — пока нет данных для распределений
- Презентация концепции инвесторам — нужна простота, не перцентили
Когда нужен Монте-Карло
- Проектирование казначейства — ключевой вопрос «хватит ли денег»
- Подбор параметров эмиссии — при каких значениях инфляция выходит из-под контроля
- Оценка runway — срок жизни проекта при разных сценариях роста
Когда нужна ABM
- Стейкинг с неравномерным распределением (киты)
- AMM и пулы ликвидности
- Governance с голосованием
- Любая система с обратной связью между участниками
Подробнее: Агентное моделирование в токеномике
Нужна симуляция для вашего проекта?
Стресс-тестируем токеномику: от Монте-Карло до агентного моделирования. Находим слабые места до запуска.
Обсудить проект →Связанные материалы
- Агентное моделирование в токеномике — ABM: код, пример стейкинга, банк-ран, инструменты
- 5 моделей предложения токенов — вестинг, bonding curve, airdrop, reward, DEX
- Bonding Curve — формулы, подбор параметров, примеры реализации