Как стать автором
Обновить

Собираем простой дэшборд токенов на TON, используя API Stonfi

Уровень сложностиПростой
Время на прочтение8 мин
Количество просмотров5.8K

Любой кто планировал покупку токенов или других цифровых активов в блокчейне встречался с проблемой сложности исследования подобных активов. Блокчейны плохо адаптированы для сбора аналитики - ноды, лайтсервера отдают информацию только по конкретному блоку сети, поэтому приходится пользоваться индексаторами блокчейна или 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&currencies=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

Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+2
Комментарии0

Публикации

Истории

Работа

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
26 октября
ProIT Network Fest
Санкт-Петербург
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань