1.1 获取 Transfer 事件日志¶
导入包¶
In [5]:
from web3 import Web3
from eth_utils import to_checksum_address
from eth_utils import to_checksum_address
from eth_abi import decode
import json
常量¶
In [6]:
try:
with open('config.json', 'r') as file:
config = json.load(file)
except FileNotFoundError:
print("The file 'config.json' was not found.")
except json.JSONDecodeError:
print("Error decoding JSON from the file.")
# 连接 Ethereum 主网节点
RPC_URL = config['RPC_URL']
w3 = Web3(Web3.HTTPProvider(RPC_URL)) # 或用其他 RPC
# ENA 合约地址(checksum 格式)
token_address = to_checksum_address(config['token_address'])
# Transfer 事件 topic
transfer_topic = config['transfer_topic']
获取并解码 Transfer 事件日志¶
In [7]:
# 查询区块范围
latest_block = w3.eth.block_number
from_block = latest_block - 3000 # 最近 3000 个区块
# 获取日志
logs = w3.eth.get_logs({
'address': token_address,
'fromBlock': from_block,
'toBlock': 'latest',
'topics': [transfer_topic]
})
print(f"抓到 {len(logs)} 条 ENA 的 Transfer 事件")
# 解码日志中的 from / to / value
def parse_transfer_log(log):
from_address = to_checksum_address("0x" + log['topics'][1].hex()[-40:])
to_address = to_checksum_address("0x" + log['topics'][2].hex()[-40:])
value = decode(['uint256'], log['data'])[0]
return from_address, to_address, value
# 打印前几条示例
for log in logs[:10]:
from_addr, to_addr, value = parse_transfer_log(log)
print(f"From: {from_addr} → To: {to_addr} | Amount: {value}")
抓到 5337 条 ENA 的 Transfer 事件 From: 0x6bBB0289DFDa178555dD1ce4AaA8f1BdbFB1bcdE → To: 0x2E1Dee213BA8d7af0934C49a23187BabEACa8764 | Amount: 10222570561101668608 From: 0x2E1Dee213BA8d7af0934C49a23187BabEACa8764 → To: 0xAe4045ffeDdF61D570E6d1fE2D71DED1A2E85a88 | Amount: 10222570561101668608 From: 0xc3Db44ADC1fCdFd5671f555236eae49f4A8EEa18 → To: 0xA69babEF1cA67A37Ffaf7a485DfFF3382056e78C | Amount: 14574256984502285475754 From: 0x0cc9E65F059bDb4d55184981AabD4D085bdFdFE3 → To: 0x6224Bc8d73d7498c2b39C65a340bd66CC69Ade62 | Amount: 0 From: 0xc3Db44ADC1fCdFd5671f555236eae49f4A8EEa18 → To: 0xA69babEF1cA67A37Ffaf7a485DfFF3382056e78C | Amount: 3356441766651113354523 From: 0xb7ef665d4556E61E36B8355d54a28143179646e3 → To: 0x5c28979a5e85DE2BC04221Bba82AD4878FA71CbA | Amount: 35372129573465823671315 From: 0xc3Db44ADC1fCdFd5671f555236eae49f4A8EEa18 → To: 0xA69babEF1cA67A37Ffaf7a485DfFF3382056e78C | Amount: 6419504662179024694881 From: 0xAe4045ffeDdF61D570E6d1fE2D71DED1A2E85a88 → To: 0x74de5d4FCbf63E00296fd95d33236B9794016631 | Amount: 21897788896532434310 From: 0x74de5d4FCbf63E00296fd95d33236B9794016631 → To: 0x74D6f431aafF3a6461adbC8909C75Dee8C1A9d85 | Amount: 21897788896532434310 From: 0xF955C57f9EA9Dc8781965FEaE0b6A2acE2BAD6f3 → To: 0x5e5b99AD5305B760E074415A6a113D2d5E87Ed6C | Amount: 854354991451532638698
1.2 获取top持币者¶
In [8]:
# 导入包
import requests
import pandas as pd
1. Ethscan API 自动版¶
In [10]:
# Ethscan Pro 版本
def fetch_token_holders():
API_KEY = config['API_KEY']
# API_KEY = 'FH5I3X12Q1SMVEC5TAKXNBS15BHJT97RE6'
# token_address = '0x57e114B691Db790C35207b2e685D4A43181e6061'
url = f'https://api.etherscan.io/api?module=token&action=tokenholderlist&contractaddress={token_address}&page=1&offset=20&apikey={API_KEY}'
response = requests.get(url)
data = response.json()
if data['status'] == '1':
holders = data['result']
df = pd.DataFrame(holders)
df['TokenHolderAddress'] = df['TokenHolderAddress'].apply(lambda x: x.lower())
df['TokenHolderQuantity'] = df['TokenHolderQuantity'].astype(float) / 1e18 # 假设 18位精度
df = df.sort_values(by='TokenHolderQuantity', ascending=False)
print(df[['TokenHolderAddress', 'TokenHolderQuantity']])
else:
print("API 请求失败:", data.get('message'))
2. Etherscan 手动获取版¶
In [11]:
# 半手动方法
# https://etherscan.io/token/0x57e114B691Db790C35207b2e685D4A43181e6061#balances
# Download
In [12]:
# 读取数据
df = pd.read_csv("export-token-0x57e114B691Db790C35207b2e685D4A43181e6061.csv")
# 清洗列名
df.columns = [c.strip().lower() for c in df.columns]
# 转换百分比为 float
df['percentage'] = df['percentage'].str.replace('%', '').astype(float)
# 可选:处理 address_nametag 为空的情况
df['address_nametag'] = df['address_nametag'].fillna('Unknown')
df
Out[12]:
rank | address | address_nametag | quantity | percentage | value | |
---|---|---|---|---|---|---|
0 | 1 | 0xc4e512313dd1ce0795f88ec5229778edf1fdf79b | Unknown | 2,677,535,328.19 | 17.8502 | $1,551,503,200.99 |
1 | 2 | 0xcfc40d4eca21f60d329f1e6b9b3d6069eaa20bbc | Unknown | 1,332,635,764.34 | 8.8842 | $772,198,458.92 |
2 | 3 | 0x2146aa5807d96e6b2922a149cee870f17347f1d0 | Unknown | 1,330,997,641.527777777777777795 | 8.8733 | $771,249,245.38 |
3 | 4 | 0x1dc55d87c4143cc1829989d7ff8d580549ac0901 | Unknown | 1,231,887,037.76 | 8.2126 | $713,819,407.80 |
4 | 5 | 0xb2af973905f05bc82bf97486b6ab883598d98298 | Unknown | 1,155,682,571.43 | 7.7046 | $669,662,577.38 |
5 | 6 | 0x8be3460a480c80728a8c4d7a5d5303c85ba7b3b9 | Ethena: sENA Token | 990,038,451.157461025792590031 | 6.6003 | $573,679,760.60 |
6 | 7 | 0xf977814e90da44bfa03b6295a0616a897441acec | Binance: Hot Wallet 20 | 886,381,042 | 5.9092 | $513,615,267.55 |
7 | 8 | 0x54b8c65f0635fd91c8729dd3269c630d9aed54e5 | Unknown | 681,913,729.671612340621111223 | 4.5461 | $395,136,274.49 |
8 | 9 | 0x877b3d5c681c8890d19dbf450306caa3c3d4bba6 | Unknown | 375,000,000 | 2.5000 | $217,294,500.00 |
9 | 10 | 0x41c7d5db1bb11a30196f4e9873b2ce417b9cba39 | Unknown | 350,000,000 | 2.3333 | $202,808,200.00 |
10 | 11 | 0xdedc15fa923d4e147875c63c0a97f85f178dfe96 | Unknown | 165,700,649 | 1.1047 | $96,015,572.46 |
11 | 12 | 0x70f58622158d7e609ae5839c4ad0d477f468863f | Unknown | 160,000,002 | 1.0667 | $92,712,321.16 |
12 | 13 | 0xf0465df588906d820f831970262bdcca0f2b3e2d | Unknown | 147,494,251.68 | 0.9833 | $85,465,839.12 |
13 | 14 | 0xa9cf4aa55c675badb68519e3cfa8f4be942e6d11 | Unknown | 135,999,992 | 0.9067 | $78,805,467.36 |
14 | 15 | 0xb829e684df8e31b402a4d4aedf3bbc18a52e7589 | Unknown | 120,000,002 | 0.8000 | $69,534,241.16 |
15 | 16 | 0x0c36fda877d41671b14fb4e7655798965237c7d0 | Unknown | 117,831,472.376023660931652455 | 0.7855 | $68,277,682.33 |
16 | 17 | 0x3c716d252b53891753de883894ff29a9ebee0f54 | Unknown | 115,949,958.74598124 | 0.7730 | $67,187,435.50 |
17 | 18 | 0x28c6c06298d514db089934071355e5743bf21d60 | Binance 14 | 108,447,417.7916789024486268 | 0.7230 | $62,840,073.13 |
18 | 19 | 0x70167b76543c4a12b49b2f2b70cbf04d99345786 | Unknown | 94,774,852.414706237138572049 | 0.6318 | $54,917,477.78 |
19 | 20 | 0x3cd9c4942467c859224325b41aeb3a4d2b84c541 | Unknown | 85,989,160.0994 | 0.5733 | $49,826,590.80 |
20 | 21 | 0x4655b6a10c83d6beff1dc7116436cfd8b4f8d48a | Unknown | 74,711,475.724211944786442383 | 0.4981 | $43,291,714.03 |
21 | 22 | 0xc882b111a75c0c657fc507c04fbfcd2cc984f071 | Gate.io 5 | 73,185,531.28345142700488998 | 0.4879 | $42,407,502.47 |
22 | 23 | 0x205188a68430e94f80a512038329030c556b50ff | Unknown | 63,475,389.541521787215571173 | 0.4232 | $36,780,941.42 |
23 | 24 | 0x6a5e25fbaf575876f2905eaa11143cbfa29bd443 | Unknown | 61,347,841.6678125 | 0.4090 | $35,548,129.55 |
24 | 25 | 0x8af2833f3aa3933a58c07e6244f9c35580f4611d | Unknown | 60,631,881.78 | 0.4042 | $35,133,265.16 |
25 | 26 | 0xbef89f2cc3c1fa89d1929d10c2421caea3d793cc | Unknown | 57,023,825.605400165677028386 | 0.3802 | $33,042,569.79 |
26 | 27 | 0xab2eedf5cf6e534b3ab8e2a71f7457228138c1b3 | Unknown | 47,639,720.16 | 0.3176 | $27,604,931.13 |
27 | 28 | 0x1157a2076b9bb22a85cc2c162f20fab3898f4101 | FalconX: 0x115...101 | 47,210,608.782906584223017985 | 0.3147 | $27,356,281.68 |
28 | 29 | 0xc31cb05ead558ed3426ec1143320540a542c9fcc | Unknown | 46,824,823.470011 | 0.3122 | $27,132,737.61 |
29 | 30 | 0xf7e9a266ee1145f6302d2cd67b1d2ba6ca400207 | Unknown | 43,750,000.999992621527777776 | 0.2917 | $25,351,025.58 |
30 | 31 | 0x22af984f13dfb5c80145e3f9ee1050ae5a5fb651 | Kraken: Cold Wallet | 38,190,824.4829 | 0.2546 | $22,129,749.63 |
31 | 32 | 0x9b0c45d46d386cedd98873168c36efd0dcba8d46 | Revolut 3 | 32,266,483.019 | 0.2151 | $18,696,878.12 |
32 | 33 | 0x4693dcf3851100c60351282dc05f9efa3b776fc8 | Unknown | 31,279,815.08 | 0.2085 | $18,125,151.41 |
33 | 34 | 0xffa8db7b38579e6a2d14f9b347a9ace4d044cd54 | Unknown | 27,995,950.2561075748818648 | 0.1866 | $16,222,309.37 |
34 | 35 | 0xf608291ed68a5ab477a4eacf5781060936970a6e | Unknown | 27,600,000 | 0.1840 | $15,992,875.20 |
35 | 36 | 0x43684d03d81d3a4c70da68febdd61029d426f042 | Unknown | 27,343,446.2245874 | 0.1823 | $15,844,214.60 |
36 | 37 | 0xc06f25517e906b7f9b4dec3c7889503bb00b3370 | Kraken: Cold Wallet 2 | 27,210,454.37509 | 0.1814 | $15,767,152.21 |
37 | 38 | 0xfab03b7d0ea9190368bfbf8f0ede5db721188f76 | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
38 | 39 | 0xf31a5763b52f67846c38c2eea22b075f66f4ea10 | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
39 | 40 | 0x1ea1097699deb7d634155bc5a046d1abbfdde487 | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
40 | 41 | 0xa055f4c2092dee7432e0699f582ef44d97b7c5eb | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
41 | 42 | 0x8ab4e48fb3ab5febba57b9f5b152c459b0303780 | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
42 | 43 | 0x454da665c949390bb755cb147c471d3c3098276f | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
43 | 44 | 0x86c74258b818b4e25516f4cdbefd41c031dbd9ab | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
44 | 45 | 0x5c8a4d04a7daffe58e6d00adb78281ce29f7e295 | Unknown | 25,312,500 | 0.1688 | $14,667,378.75 |
45 | 46 | 0x02bda4efec1b116a563c6d05a60f617f9eda7df4 | Unknown | 25,000,000 | 0.1667 | $14,486,300.00 |
46 | 47 | 0x629e44441c6001e63483803202c579dba0722b2f | Unknown | 24,390,243.9 | 0.1626 | $14,132,975.61 |
47 | 48 | 0x90c74dfe1ff09789cbdaa4514d80b8d8043ccf51 | Unknown | 23,819,597.76 | 0.1588 | $13,802,313.56 |
48 | 49 | 0xdfd5293d8e347dfe59e90efd55b2956a1343963d | Binance 16 | 22,337,182.09399579 | 0.1489 | $12,943,324.84 |
49 | 50 | 0xd322e4c49b3cb0d0c15e8a956f44ea56ccdfb97a | Unknown | 21,875,000 | 0.1458 | $12,675,512.50 |
In [13]:
# top 50 持币地址
df['address']
Out[13]:
0 0xc4e512313dd1ce0795f88ec5229778edf1fdf79b 1 0xcfc40d4eca21f60d329f1e6b9b3d6069eaa20bbc 2 0x2146aa5807d96e6b2922a149cee870f17347f1d0 3 0x1dc55d87c4143cc1829989d7ff8d580549ac0901 4 0xb2af973905f05bc82bf97486b6ab883598d98298 5 0x8be3460a480c80728a8c4d7a5d5303c85ba7b3b9 6 0xf977814e90da44bfa03b6295a0616a897441acec 7 0x54b8c65f0635fd91c8729dd3269c630d9aed54e5 8 0x877b3d5c681c8890d19dbf450306caa3c3d4bba6 9 0x41c7d5db1bb11a30196f4e9873b2ce417b9cba39 10 0xdedc15fa923d4e147875c63c0a97f85f178dfe96 11 0x70f58622158d7e609ae5839c4ad0d477f468863f 12 0xf0465df588906d820f831970262bdcca0f2b3e2d 13 0xa9cf4aa55c675badb68519e3cfa8f4be942e6d11 14 0xb829e684df8e31b402a4d4aedf3bbc18a52e7589 15 0x0c36fda877d41671b14fb4e7655798965237c7d0 16 0x3c716d252b53891753de883894ff29a9ebee0f54 17 0x28c6c06298d514db089934071355e5743bf21d60 18 0x70167b76543c4a12b49b2f2b70cbf04d99345786 19 0x3cd9c4942467c859224325b41aeb3a4d2b84c541 20 0x4655b6a10c83d6beff1dc7116436cfd8b4f8d48a 21 0xc882b111a75c0c657fc507c04fbfcd2cc984f071 22 0x205188a68430e94f80a512038329030c556b50ff 23 0x6a5e25fbaf575876f2905eaa11143cbfa29bd443 24 0x8af2833f3aa3933a58c07e6244f9c35580f4611d 25 0xbef89f2cc3c1fa89d1929d10c2421caea3d793cc 26 0xab2eedf5cf6e534b3ab8e2a71f7457228138c1b3 27 0x1157a2076b9bb22a85cc2c162f20fab3898f4101 28 0xc31cb05ead558ed3426ec1143320540a542c9fcc 29 0xf7e9a266ee1145f6302d2cd67b1d2ba6ca400207 30 0x22af984f13dfb5c80145e3f9ee1050ae5a5fb651 31 0x9b0c45d46d386cedd98873168c36efd0dcba8d46 32 0x4693dcf3851100c60351282dc05f9efa3b776fc8 33 0xffa8db7b38579e6a2d14f9b347a9ace4d044cd54 34 0xf608291ed68a5ab477a4eacf5781060936970a6e 35 0x43684d03d81d3a4c70da68febdd61029d426f042 36 0xc06f25517e906b7f9b4dec3c7889503bb00b3370 37 0xfab03b7d0ea9190368bfbf8f0ede5db721188f76 38 0xf31a5763b52f67846c38c2eea22b075f66f4ea10 39 0x1ea1097699deb7d634155bc5a046d1abbfdde487 40 0xa055f4c2092dee7432e0699f582ef44d97b7c5eb 41 0x8ab4e48fb3ab5febba57b9f5b152c459b0303780 42 0x454da665c949390bb755cb147c471d3c3098276f 43 0x86c74258b818b4e25516f4cdbefd41c031dbd9ab 44 0x5c8a4d04a7daffe58e6d00adb78281ce29f7e295 45 0x02bda4efec1b116a563c6d05a60f617f9eda7df4 46 0x629e44441c6001e63483803202c579dba0722b2f 47 0x90c74dfe1ff09789cbdaa4514d80b8d8043ccf51 48 0xdfd5293d8e347dfe59e90efd55b2956a1343963d 49 0xd322e4c49b3cb0d0c15e8a956f44ea56ccdfb97a Name: address, dtype: object
In [14]:
import matplotlib.pyplot as plt
top10 = df.head(10)
# 柱状图
plt.figure(figsize=(10, 6))
plt.barh(top10['address'], top10['percentage'])
plt.xlabel("Proportion of token holdings (%)")
plt.title("Pic 1. ENA top 10 address persent(bar chart)")
plt.gca().invert_yaxis()
plt.tight_layout()
plt.show()
# 饼图
plt.figure(figsize=(8, 8))
plt.pie(top10['percentage'], labels=top10['address_nametag'], autopct='%1.1f%%')
plt.title("Pic 2. ENA top 10 address persent(pie chart)")
plt.tight_layout()
plt.show()
2. 基于santiment 链上分析¶
2.1 导入包¶
In [16]:
import requests
import datetime
import pandas as pd
import matplotlib.pyplot as plt
import time
2.2 常量¶
In [17]:
# Santiment API Token
santiment_API = config["santiment_API"]
API_TOKEN = santiment_API
API_KEY = santiment_API
HEADERS = {'Authorization': f'Apikey {API_TOKEN}'}
# 分析代币标识(slug)
ASSET = 'ethena' # ENA 的 slug 在 Santiment 中为 'ethena'
# 时间范围
START_DATE = '2024-07-01'
END_DATE = '2025-08-01'
INTERVAL = '1d'
2.3 函数定义:获取活跃地址数¶
In [18]:
# 获取活跃地址数
def get_active_addresses(asset, from_date, to_date, interval='1d'):
url = f"https://api.santiment.net/graphql"
query = """
{
getMetric(metric: "active_addresses_24h") {
timeseriesData(
slug: "%s"
from: "%sT00:00:00Z"
to: "%sT00:00:00Z"
interval: "%s"
) {
datetime
value
}
}
}
""" % (asset, from_date, to_date, interval)
response = requests.post(url, json={'query': query}, headers=HEADERS)
data = response.json()
timeseries = data['data']['getMetric']['timeseriesData']
df = pd.DataFrame(timeseries)
df['datetime'] = pd.to_datetime(df['datetime'])
df.set_index('datetime', inplace=True)
return df
2.4 函数定义:获取 Exchange 流入量¶
In [19]:
# 获取 Exchange 流入量
def get_exchange_inflow(asset, from_date, to_date, interval='1d'):
url = f"https://api.santiment.net/graphql"
query = """
{
getMetric(metric: "exchange_inflow") {
timeseriesData(
slug: "%s"
from: "%sT00:00:00Z"
to: "%sT00:00:00Z"
interval: "%s"
) {
datetime
value
}
}
}
""" % (asset, from_date, to_date, interval)
response = requests.post(url, json={'query': query}, headers=HEADERS)
data = response.json()
timeseries = data['data']['getMetric']['timeseriesData']
df = pd.DataFrame(timeseries)
df['datetime'] = pd.to_datetime(df['datetime'])
df.set_index('datetime', inplace=True)
return df
2.5 *fetch & save price data 无需每次执行¶
In [20]:
# 获取数据并保存
active_df = get_active_addresses(ASSET, START_DATE, END_DATE)
# 保存数据到本地
active_df.to_pickle("active_data.pkl")
time.sleep(2)
inflow_df = get_exchange_inflow(ASSET, START_DATE, END_DATE)
inflow_df.to_pickle("exchange_inflow_data.pkl")
2.6 活跃地址与CEX流入数据分析¶
In [21]:
# 读取数据
active_df = pd.read_pickle("active_data.pkl")
inflow_df = pd.read_pickle("exchange_inflow_data.pkl")
fig, ax1 = plt.subplots(figsize=(12, 6))
ax1.plot(active_df.index, active_df['value'], color='orange', label="Active Addresses (24h)")
ax1.set_ylabel("Active Addresses (24h)", color='orange')
ax1.tick_params(axis='y', labelcolor='orange')
ax2 = ax1.twinx()
ax2.plot(inflow_df.index, inflow_df['value'], color='blue', label="Exchange Inflow")
ax2.set_ylabel("Exchange Inflow", color='blue')
ax2.tick_params(axis='y', labelcolor='blue')
plt.title(f"Pic 1. ENA active address & CEX inflow")
plt.show()
2.7 函数定义: 获取 ENA 价格时间序列¶
In [22]:
# 获取 ENA 价格时间序列
def get_price_via_graphql(slug, from_date, to_date, interval="1d"):
url = "https://api.santiment.net/graphql"
headers = {
"Authorization": f"Apikey {API_KEY}",
"Content-Type": "application/json"
}
query = f"""
{{
getMetric(metric: "price_usd") {{
timeseriesData(
slug: "{slug}",
from: "{from_date}T00:00:00Z",
to: "{to_date}T00:00:00Z",
interval: "{interval}"
) {{
datetime
value
}}
}}
}}
"""
# Request Data
resp = requests.post(url, json={"query": query}, headers=headers)
data = resp.json()
ts = data['data']['getMetric']['timeseriesData']
df = pd.DataFrame(ts)
df['datetime'] = pd.to_datetime(df['datetime'])
df.set_index('datetime', inplace=True)
df.rename(columns={'value': 'price_usd'}, inplace=True)
return df
2.8 *fetch & save price data 无需每次执行¶
In [23]:
# fetch & save price data 无需每次执行
price_df = get_price_via_graphql("ethena", START_DATE, END_DATE)
# 保存数据到本地
price_df.to_pickle("ethena_price_data.pkl")
2.9 活跃地址与ENA价格关联分析¶
In [24]:
# 读取数据
price_df = pd.read_pickle("ethena_price_data.pkl")
fig, ax1 = plt.subplots(figsize=(12, 6))
ax1.plot(price_df.index, price_df['price_usd'], color='orange', label="ENA Price")
ax1.set_ylabel("Price USD", color='orange')
ax1.tick_params(axis='y', labelcolor='orange')
ax2 = ax1.twinx()
ax2.plot(active_df.index, active_df['value'], color='blue', label="active address")
ax2.set_ylabel("active address", color='blue')
ax2.tick_params(axis='y', labelcolor='blue')
plt.title("Pic 2. ENA Price vs active address")
plt.show()
2.10 函数定义:获取交易所 流入|流出 数据¶
In [25]:
def get_exchange_flow(slug, from_date, to_date, flow_type):
url = "https://api.santiment.net/graphql"
headers = {
"Authorization": f"Apikey {API_KEY}",
"Content-Type": "application/json"
}
query = f"""
{{
getMetric(metric: "{flow_type}") {{
timeseriesData(
slug: "{slug}",
from: "{from_date}T00:00:00Z",
to: "{to_date}T00:00:00Z",
interval: "1d"
) {{
datetime
value
}}
}}
}}
"""
resp = requests.post(url, json={"query": query}, headers=headers)
data = resp.json()
ts = data['data']['getMetric']['timeseriesData']
df = pd.DataFrame(ts)
df['datetime'] = pd.to_datetime(df['datetime'])
df.set_index('datetime', inplace=True)
return df
2.11 *fetch & save CEX in&out flow data 无需每次执行¶
In [26]:
time.sleep(2)
inflow_df = get_exchange_flow("ethena", START_DATE, END_DATE, "exchange_inflow")
# 保存数据到本地
inflow_df.to_pickle("exchange_inflow_data.pkl")
time.sleep(2)
outflow_df = get_exchange_flow("ethena", START_DATE, END_DATE, "exchange_outflow")
# 保存数据到本地
outflow_df.to_pickle("exchange_outflow_data.pkl")
time.sleep(2)
2.12 ENA price & exchange_inflow_data analysis¶
In [27]:
# 1. 读取pickle文件
inflow_df = pd.read_pickle('exchange_inflow_data.pkl')
# 2. 查看数据结构
print(inflow_df.head())
print(inflow_df.columns)
# 假设DataFrame有datetime索引和'value'列(Santiment的GraphQL结果一般是这样)
# 如果不是datetime索引,需要转化
if not isinstance(inflow_df.index, pd.DatetimeIndex):
inflow_df['datetime'] = pd.to_datetime(inflow_df['datetime'])
inflow_df.set_index('datetime', inplace=True)
# 3. 绘制交易所流入时间序列图
plt.figure(figsize=(14,6))
plt.plot(inflow_df.index, inflow_df['value'], label='Exchange Inflow')
plt.title('ENA Token Exchange Inflow Over Time')
plt.xlabel('Date')
plt.ylabel('Token Amount')
plt.legend()
plt.grid(True)
plt.show()
# 4. 简单统计分析示例:计算每日流入均值、峰值等
mean_inflow = inflow_df['value'].mean()
max_inflow = inflow_df['value'].max()
print(f"Average daily inflow: {mean_inflow:.2f}")
print(f"Maximum daily inflow: {max_inflow:.2f}")
# 5. 进阶:和价格数据对比(假设你有price_df)
# 合并两个DataFrame
# price_df已经有datetime索引和'price_usd'列
combined_df = inflow_df.join(price_df['price_usd'], how='inner')
fig, ax1 = plt.subplots(figsize=(14,6))
color = 'tab:blue'
ax1.set_xlabel('Date')
ax1.set_ylabel('Exchange Inflow', color=color)
ax1.plot(combined_df.index, combined_df['value'], color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()
color = 'tab:orange'
ax2.set_ylabel('Price USD', color=color)
ax2.plot(combined_df.index, combined_df['price_usd'], color=color)
ax2.tick_params(axis='y', labelcolor=color)
plt.title('ENA Exchange Inflow vs Price')
plt.show()
value datetime 2024-08-11 00:00:00+00:00 2.142636e+06 2024-08-12 00:00:00+00:00 1.005877e+07 2024-08-13 00:00:00+00:00 1.026039e+07 2024-08-14 00:00:00+00:00 3.340458e+06 2024-08-15 00:00:00+00:00 7.058729e+06 Index(['value'], dtype='object')
Average daily inflow: 22927086.33 Maximum daily inflow: 202353475.26
2.13 ENA Price & analysis exchange_outflow_data¶
In [28]:
# 1. 读取pickle文件
outflow_df = pd.read_pickle('exchange_outflow_data.pkl')
# 2. 查看数据结构
print(outflow_df.head())
print(outflow_df.columns)
# 假设DataFrame有datetime索引和'value'列(Santiment的GraphQL结果一般是这样)
# 如果不是datetime索引,需要转化
if not isinstance(outflow_df.index, pd.DatetimeIndex):
outflow_df['datetime'] = pd.to_datetime(outflow_df['datetime'])
outflow_df.set_index('datetime', inplace=True)
# 3. 绘制交易所流入时间序列图
plt.figure(figsize=(14,6))
plt.plot(outflow_df.index, outflow_df['value'], label='Exchange Outflow')
plt.title('ENA Token Exchange Outflow Over Time')
plt.xlabel('Date')
plt.ylabel('Token Amount')
plt.legend()
plt.grid(True)
plt.show()
# 4. 简单统计分析示例:计算每日流入均值、峰值等
mean_outflow = outflow_df['value'].mean()
max_outflow = outflow_df['value'].max()
print(f"Average daily outflow: {mean_outflow:.2f}")
print(f"Maximum daily outflow: {max_outflow:.2f}")
# 5. 进阶:和价格数据对比(假设你有price_df)
# 合并两个DataFrame
# price_df已经有datetime索引和'price_usd'列
combined_df = outflow_df.join(price_df['price_usd'], how='inner')
fig, ax1 = plt.subplots(figsize=(14,6))
color = 'tab:blue'
ax1.set_xlabel('Date')
ax1.set_ylabel('Exchange Outflow', color=color)
ax1.plot(combined_df.index, combined_df['value'], color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()
color = 'tab:orange'
ax2.set_ylabel('Price USD', color=color)
ax2.plot(combined_df.index, combined_df['price_usd'], color=color)
ax2.tick_params(axis='y', labelcolor=color)
plt.title('ENA Exchange Outflow vs Price')
plt.show()
value datetime 2024-08-11 00:00:00+00:00 2.281849e+06 2024-08-12 00:00:00+00:00 2.047038e+07 2024-08-13 00:00:00+00:00 9.898722e+06 2024-08-14 00:00:00+00:00 4.945018e+06 2024-08-15 00:00:00+00:00 5.945851e+06 Index(['value'], dtype='object')
Average daily outflow: 19693605.60 Maximum daily outflow: 287706783.95
In [ ]: