Собираем простой дэшборд токенов на TON используя API Stonfi
Любой кто планировал покупку токенов или других цифровых активов в блокчейне встречался с проблемой сложности исследования подобных активов. Блокчейны плохо адаптированы для сбора аналитики — ноды, лайтсервера отдают информацию только по конкретному блоку сети, поэтому приходится пользоваться индексаторами блокчейна или API сервисов работающих на блокчейне.
Так как децентрализованные биржи стремятся присутствовать на таких площадках как coingecko они создают стандартизированный API, часто делая их открытыми. Подобные API позволяют достать много информации связанной с торговлей токенами.
Поэтому в данной статье мы соберем дэшборд для токенов на блокчейне TON и увидим на сколько просто достать данные из DEX, но в конце поговорим про проблемы такого метода сбора данных. Надеюсь подобный простой туториал, станет понятным и приятным шагов в блокчейне TON.
Прежде чем мы перейдем к сбору данных, несколько дисклеймеров:
код в туториале максимально простой, чтобы его можно было прочитать по диагонали
жеттоны это стандарт взаимозаменяемых токенов на TON
Вспомогательные функции
Прежде чем вызвать ручку, напишем вспомогательные функции, которые помогут нам с параметрами для вызываемых API, а именно определить даты и привести в нужный нам формат:
def now_utc():
return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
def thirty_days_utc():
return (datetime.datetime.utcnow() - datetime.timedelta(days=30)).strftime('%Y-%m-%dT%H:%M:%S')
def seven_days_utc():
return (datetime.datetime.utcnow() - datetime.timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
def day_utc():
return (datetime.datetime.utcnow() - datetime.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
now_utc()
Вызовем API GET https://api.ston.fi/v1/stats/pool
payload = {'since': day_utc(), 'until': now_utc()}
r = requests.get('https://api.ston.fi/v1/stats/pool', params=payload)
r.json()['stats'][0]
Получим разнообразные параметры пула:
Рандомный пул — все токены в данном туториале случайны
Теперь попробуем собрать нужную нам информацию
Берем поля
Для нашего дэшборда нам понадобится:
Имя и символ жеттона
Последняя цена Жеттона за выбранный период
Объем в TON
Ссылка на пул для свапа
Выберем эти данные используя наше API:
#Соберем нужные нам поля
def take_pool_stats():
temp_arr=[]
payload = {'since': day_utc(), 'until': now_utc()}
# stonfi pools stats
r = requests.get('https://api.ston.fi/v1/stats/pool', params=payload)
resp = r.json()['stats']
for jetton in resp:
temp_dict = {'coin': jetton['base_name'],'pair': jetton['base_symbol']+'/'+jetton['quote_symbol'],'url': jetton['url'],'price_ton': jetton['last_price'],'volume_ton': jetton['quote_volume']}
temp_arr.append(temp_dict)
return temp_arr
take_pool_stats()
Увидим вот такие json«ы:
```
{'coin': 'Chow Chow',
'pair': 'CHOW/TON',
'url': 'https://app.ston.fi/swap? ft=EQBtWFPgVknfzu6xaVcRBbNP3h_6UJ_xe29sVkiFTyPiv2bq&tt=EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez',
'price_ton': '0.010646867',
'volume_ton': '0.000000000'},
{'coin': 'Tonald Duck',
'pair': 'TDUCK/TON',
'url': 'https://app.ston.fi/swap? ft=EQBUGgcu-h4SqMh5hrentq4vE59tBRRfrYE3H_0s6D_1Xzsa&tt=EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez',
'price_ton': '0.000000363',
'volume_ton': '0.000000000'},
```
Не удивляйтесь, что объем равен нулю — пул может создать любой, соответственно не все они супер востребованы.
Шаг назад
Смотреть цены в TON неудобно, поэтому для представления цен в долларах получим текущее значение TON/USD. Для этого воспользуемся tonapi.io для нашей задачи, бесплатных лимитов вполне хватит:
#take toncoin price
def take_ton_price():
resp = requests.get('https://tonapi.io/v2/rates?tokens=ton¤cies=usd')
# Ex: {"rates":{"TON":{"prices":{"USD":2.1215},"diff_24h":{"USD":"+0.85%"},"diff_7d":{"USD":"-6.04%"},"diff_30d":{"USD":"+2.75%"}}}}
return resp.json()["rates"]["TON"]["prices"]["USD"]
take_ton_price()
Уберем лишнее
Используем полученный курс для пересчета объема и цены. Как упоминалось выше API отдаем вообще все пулы, это значит, что туда могут попасть невостребованные пулы у которых объем равен нулю. А также пулы между токенами, которые нам не интересны, так как они не отображают цену и объем торгово относительно TON или доллара. Соответственно:
выберем пулы c TON
уберем пулы в которых последняя цена равна нулю
уберем пулы где объем торговли за период меньше 1000 долларов
И сразу же отсортируем по объему:
def take_pool_stats():
temp_arr=[]
payload = {'since': day_utc(), 'until': now_utc()}
# stonfi pools stats
r = requests.get('https://api.ston.fi/v1/stats/pool', params=payload)
resp = r.json()['stats']
# ton_usd
ton_usd = take_ton_price()
for jetton in resp:
temp_dict = {'coin': jetton['base_name'],'pair': jetton['base_symbol']+'/'+jetton['quote_symbol'],'url': jetton['url'],'price_ton': jetton['last_price'],'volume_ton': jetton['quote_volume'], 'price_usd': round(float(jetton['last_price'])*ton_usd,2),'volume_usd': round(float(jetton['quote_volume'])*ton_usd,2)}
if(jetton['quote_symbol']=='TON' and int(float(jetton['last_price']) != 0) and int(float(jetton['quote_volume']) != 0) and int(temp_dict['volume_usd']) > 1000):
temp_arr.append(temp_dict)
return sorted(temp_arr, key=lambda d: d['volume_usd'],reverse=True)
# Coin - base_name
# Pair - base_symbol/quote_symbol url
# Price - last_price * TON in USD price
# 24 Volume - не понятно чекнуть по coingecko
take_pool_stats()
Увидим вот таки json«ы:
```
{'coin': 'STON',
'pair': 'STON/TON',
'url': 'https://app.ston.fi/swap? ft=EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO&tt=EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez',
'price_ton': '2.220362684',
'volume_ton': '74651.187439449',
'price_usd': 5.89,
'volume_usd': 197926.43},
```
Процент от общего
Объем удобно рассматривать от всего объем на рынке, поэтому посчитаем сумму объема и добавим процент.(В коде можно увидеть, что я оставил аж 10 цифр после запятой, связано это с тем, что недавно в TON проходила инициатива по добавлению ликвидности в пулы, в связи с чем объемы сильно «размыло». В обычной ситуации, хватило бы и 4 знаков)
def take_pool_stats(payload):
temp_arr=[]
# stonfi pools stats
r = requests.get('https://api.ston.fi/v1/stats/pool', params=payload)
resp = r.json()['stats']
# ton_usd
ton_usd = take_ton_price()
# for volume percentage
all_volume = sum(float(item['quote_volume']) for item in resp)
print(all_volume)
for jetton in resp:
temp_dict = {'coin': jetton['base_name'],'pair': jetton['base_symbol']+'/'+jetton['quote_symbol'],'url': jetton['url'],'price_ton': jetton['last_price'],'volume_ton': jetton['quote_volume'], 'price_usd': round(float(jetton['last_price'])*ton_usd,4),'volume_usd': round(float(jetton['quote_volume'])*ton_usd,2),"volume_perc": round((float(jetton['quote_volume'])/all_volume)*100,10)}
if(jetton['quote_symbol']=='TON' and int(float(jetton['last_price']) != 0) and int(float(jetton['quote_volume']) != 0) and int(temp_dict['volume_usd']) > 1000):
temp_arr.append(temp_dict)
return sorted(temp_arr, key=lambda d: d['volume_usd'],reverse=True)
payload = {'since': day_utc(), 'until': now_utc()}
take_pool_stats(payload)
Положим полученный список в датафрейм библиотеки Pandas для удобства отображения и получим:
Используя такой простой пример, вы можете тестировать свои гипотезы связанные с жеттонами, например посмотрев динамику за несколько дней или рассматривая разные периоды. Но почти любое исследование приводит к необходимости детализации информации по пулам. Ведь прекрасно понятно, что можно нагонять объем с пары кошельков, таким образом разгоняя токен.Для этого нужно собрать информацию по операциям.
Собираем информацию по операциям
В API стонфай есть отдельный метод, которые возвращает операций. Удобно, то, что для каждой операции прописывается пул в котором была совершена операция. Этим мы и воспользуемся, чтобы посчитать количество свапов по пулам и посмотреть сколько уникальных кошельков среди этих операций.
Для начала достанем операции за период:
def take_operations(payload):
r = requests.get('https://api.ston.fi/v1/stats/operations', params=payload)
return r.json()["operations"]
payload = {'since': day_utc(), 'until': now_utc()}
pool_oper_list = list(filter(lambda person: person['operation']['pool_address'] == 'EQB4whdcKBgsDHoG2j9RhTdMdghXAIyPqQzusLSUgMUNUseo', take_operations(payload)))
len(pool_oper_list)
Операции в пулах бывают разные, есть свапы, когда пользователи обменивают жеттоны и TON, есть добавление ликвидности в пулы, возможные операции могут отличаться от биржи к бирже. Для нашего простого примера мы возьмем только свапы.
Соберем функцию, которая для каждого выбранного нами пула вернет количество свапов и количество уникальных кошельков совершивших данные свапы.
def count_pool_operations(operations,addr_str):
pool_oper_list = list(filter(lambda person: person['operation']['pool_address'] == addr_str, operations))
unique_counts = collections.Counter(e['operation']['destination_wallet_address'] for e in pool_oper_list )
return len(pool_oper_list),len(set(unique_counts))
Добавим эту информацию в дэшборд:
def take_pool_stats(payload):
temp_arr=[]
# stonfi pools stats
r = requests.get('https://api.ston.fi/v1/stats/pool', params=payload)
resp = r.json()['stats']
# ton_usd
ton_usd = take_ton_price()
# for volume percentage
all_volume = sum(float(item['quote_volume']) for item in resp)
# take operations
payload['op_type'] = 'Swap'
operations = take_operations(payload)
for jetton in resp:
temp_dict = {'pool_address': jetton['pool_address'],'coin': jetton['base_name'],'pair': jetton['base_symbol']+'/'+jetton['quote_symbol'],'url': jetton['url'],'price_ton': jetton['last_price'],'volume_ton': jetton['quote_volume'], 'price_usd': round(float(jetton['last_price'])*ton_usd,4),'volume_usd': round(float(jetton['quote_volume'])*ton_usd,2),"volume_perc": round((float(jetton['quote_volume'])/all_volume)*100,2)}
#temp_dict = {'coin': jetton['base_name'],'pair': jetton['base_symbol']+'/'+jetton['quote_symbol'],'url': jetton['url'],'price_ton': jetton['last_price'],'volume_ton': jetton['quote_volume'], 'price_usd': round(float(jetton['last_price'])*ton_usd,2),'volume_usd': round(float(jetton['quote_volume'])*ton_usd,2)}
#temp_dict = {'coin': jetton['base_name'],'pair': jetton['base_symbol']+'/'+jetton['quote_symbol'],'url': jetton['url'],'price_ton': jetton['last_price'],'volume_ton': jetton['quote_volume'], 'price_usd': jetton['last_price'],'volume_usd': jetton['quote_volume']}
if(jetton['quote_symbol']=='TON' and int(float(jetton['last_price']) != 0) and int(float(jetton['quote_volume']) != 0) and int(temp_dict['volume_usd']) > 1000):
#добавим информацию по операциям
temp_pool_count = count_pool_operations(operations,temp_dict['pool_address'])
temp_dict["count_swaps"] = temp_pool_count[0]
temp_dict["count_unique"] = temp_pool_count[1]
temp_arr.append(temp_dict)
return sorted(temp_arr, key=lambda d: d['volume_usd'],reverse=True)
# Coin - base_name
# Pair - base_symbol/quote_symbol url
# Price - last_price * TON in USD price
# 24 Volume - не понятно чекнуть по coingecko
payload = {'since': day_utc(), 'until': now_utc()}
take_pool_stats(payload)
Получим:
Ложка дегтя
Пример, который мы собрали выше показывает насколько легко собрать данные по пулам и операциям, но есть и проблемы.
Так как задача API отдавать статистические данные, по цене токены мы получаем последнюю цену за период, что с точки зрения аналитики не очень корректно. Поэтому в случае, если нам важна цена токена за период, важно будет посчитать цену взвешенную по времени, что приведет к увеличению количества запросов API.
Второй проблемой, подобного сбора данных, является время, которое уходит на один запрос, API децентрализованных бирж не адаптированы под глубокие запросы, поэтому если вам интересна некая live аналитика, то единственный выход, это собирать данные непосредственно из смарт-контрактов — вызывая get-методы.
## Заключение
Мне нравится блокчейн TON своей технической изящностью, как минимум это не очередная копия Ethereum, которую разгоняют с помощью большого капитала без оглядки, а вообще зачем это нужно пользователю. Если вы хотите узнать больше о блокчейне TON, у меня есть опенсорсные уроки, благодаря которым вы научитесь создавать полноценные приложения на TON.
https://github.com/romanovichim/TonFunClessons_ru
Новые туториалы и дата аналитику я кидаю сюда: https://t.me/ton_learn