3
3
4
4
class Token :
5
5
"""
6
- A class to represent a leveraged token on Toros Finance
6
+ A class to represent a leveraged token on Toros Finance.
7
7
"""
8
+ CHAIN_IDS = {
9
+ "POL" : 137 , # Polygon
10
+ "OP" : 10 , # Optimism
11
+ "ARB" : 42161 , # Arbitrum
12
+ "BASE" : 8453 , # Base
13
+ }
14
+ TOROS_URL = "https://toros.finance/_next/data/mw471zlJ9uL1Ee-_If1FI/category/leverage.json?category=leverage"
15
+ GRAPHQL_URL = "https://api-v2.dhedge.org/graphql"
16
+ SCALE_FACTOR = 10 ** 18
17
+
8
18
def __init__ (self , ticker : str ) -> None :
9
19
"""
10
- Initialize the Token with a contract address .
20
+ Initialize the Token with a ticker string .
11
21
12
- :param symbol : str: The token's contract address (e.g., "ARB:BTCBULL3X").
22
+ :param ticker : str: The token's ticker in "CHAIN:SYMBOL" format (e.g., "ARB:BTCBULL3X").
13
23
"""
24
+ if ':' not in ticker :
25
+ raise ValueError ("Ticker must be in 'CHAIN:SYMBOL' format." )
26
+
14
27
self .ticker : str = ticker
15
- splits = ticker .split (':' )
16
- self .chain_name : str = splits [0 ]
17
- self .symbol : str = splits [1 ]
18
-
19
- def _get_chain_id (self , chain_name : str ) -> int :
20
- chain_names = {
21
- "POL" : 137 , # Polygon
22
- "OP" : 10 , # Optimism
23
- "ARB" : 42161 , # Arbitrum
24
- "BASE" : 8453 , # Base
25
- }
26
-
27
- chain_id = chain_names .get (chain_name .upper ())
28
+ self .chain_name , self .symbol = ticker .split (':' , 1 )
29
+
30
+ def _get_chain_id (self ) -> int :
31
+ chain_id = self .CHAIN_IDS .get (self .chain_name .upper ())
28
32
if chain_id is None :
29
- raise ValueError (f"Chain ID not found for chain name: { chain_name } " )
30
-
33
+ raise ValueError (f"Invalid chain name '{ self .chain_name } '. Valid options: { ', ' .join (self .CHAIN_IDS .keys ())} " )
31
34
return chain_id
32
35
33
- def _get_token_address (self , chain_name : str , symbol : str ) -> str :
34
- chain_id = self ._get_chain_id (chain_name )
35
-
36
- url = "https://toros.finance/_next/data/mw471zlJ9uL1Ee-_If1FI/category/leverage.json?category=leverage"
37
- response = requests .get (url )
36
+ def _get_token_address (self ) -> str :
37
+ response = requests .get (self .TOROS_URL , timeout = 10 )
38
38
response .raise_for_status ()
39
39
data = response .json ()
40
40
41
- products = data .get ('pageProps' , {}).get ('products' , {})
42
-
41
+ chain_id = self ._get_chain_id ()
42
+ products = data .get ('pageProps' , {}).get ('products' , [])
43
+
43
44
for product in products :
44
- if product .get ('chainId' ) == chain_id and product .get ('symbol' ) == symbol :
45
+ if product .get ('chainId' ) == chain_id and product .get ('symbol' ) == self . symbol :
45
46
return product .get ('address' )
46
-
47
- raise ValueError (f"Token with symbol '{ symbol } ' and chain '{ chain_name } ' not found." )
47
+
48
+ raise ValueError (f"Token with symbol '{ self . symbol } ' and chain '{ self . chain_name } ' not found." )
48
49
49
50
def history (self , period : str = "1y" , interval : str = "1d" ) -> pd .DataFrame :
50
51
"""
51
52
Fetch historical data for the token.
52
53
53
- :param period: str: The period of price history (can be "1d", "1w", "1m", or "1y", default is "1y").
54
- :param interval: str: The interval for data points (can be "1h", "4h", "1d", "1w", default is "1d ").
54
+ :param period: str: Period of price history ("1d", "1w", "1m", "1y").
55
+ :param interval: str: Interval for data points ("1h", "4h", "1d", "1w").
55
56
:return: pd.DataFrame: Token's price history as a DataFrame.
56
57
"""
57
- address = self ._get_token_address (self .chain_name , self .symbol )
58
- url = "https://api-v2.dhedge.org/graphql"
58
+ valid_periods = {"1d" , "1w" , "1m" , "1y" }
59
+ valid_intervals = {"1h" , "4h" , "1d" , "1w" }
60
+
61
+ if period not in valid_periods :
62
+ raise ValueError (f"Invalid period '{ period } '. Valid options: { ', ' .join (valid_periods )} " )
63
+ if interval not in valid_intervals :
64
+ raise ValueError (f"Invalid interval '{ interval } '. Valid options: { ', ' .join (valid_intervals )} " )
65
+
66
+ address = self ._get_token_address ()
59
67
payload = {
60
- "query" : "query GetTokenPriceCandles($address: String!, $period: String!, $interval: String) {\n tokenPriceCandles(address: $address, period: $period, interval: $interval) {\n timestamp\n open\n close\n max\n min\n }\n }\n " ,
68
+ "query" : """query GetTokenPriceCandles($address: String!, $period: String!, $interval: String) {
69
+ tokenPriceCandles(address: $address, period: $period, interval: $interval) {
70
+ timestamp
71
+ open
72
+ close
73
+ max
74
+ min
75
+ }
76
+ }""" ,
61
77
"variables" : {
62
78
"address" : address ,
63
79
"period" : period ,
@@ -66,30 +82,25 @@ def history(self, period: str = "1y", interval: str = "1d") -> pd.DataFrame:
66
82
"operationName" : "GetTokenPriceCandles"
67
83
}
68
84
69
- response = requests .post (url , json = payload )
85
+ response = requests .post (self . GRAPHQL_URL , json = payload , timeout = 10 )
70
86
response .raise_for_status ()
71
87
data = response .json ()
72
88
73
89
candles = data .get ("data" , {}).get ("tokenPriceCandles" , [])
74
90
if not candles :
75
91
raise ValueError ("No data returned for the specified parameters." )
76
92
77
- df = pd .DataFrame (candles )
78
- df = df .rename (columns = {
93
+ df = pd .DataFrame (candles ).rename (columns = {
79
94
"timestamp" : "Date" ,
80
95
"open" : "Open" ,
81
96
"close" : "Close" ,
82
97
"max" : "High" ,
83
98
"min" : "Low"
84
99
})
85
-
86
- df ["Date" ] = pd .to_numeric (df ["Date" ], errors = 'coerce' )
87
- df ["Date" ] = pd .to_datetime (df ["Date" ], unit = 'ms' )
100
+
101
+ df ["Date" ] = pd .to_datetime (pd .to_numeric (df ["Date" ], errors = 'coerce' ), unit = 'ms' )
88
102
df .set_index ("Date" , inplace = True )
89
-
90
- scale_factor = 10 ** 18
91
103
df [["Open" , "Close" , "High" , "Low" ]] = df [["Open" , "Close" , "High" , "Low" ]].apply (
92
- lambda x : x .astype (int ) / scale_factor
104
+ lambda x : x .astype (float ) / self . SCALE_FACTOR
93
105
)
94
-
95
- return df
106
+ return df
0 commit comments