Skip to content

Commit e5d8275

Browse files
committed
Implement new market recommender page and enhance backend logic
- Introduced a new Streamlit page for the market recommender, providing users with recommendations for listing, delisting, or adjusting leverage based on various metrics. - Updated the `market_recommender.py` file to include detailed scoring logic based on Drift Protocol metrics, including volume, open interest, and fully diluted valuation. - Enhanced the backend API to support fetching market recommendations, integrating new scoring functions and recommendation logic. - Enhanced the cache middleware to prevent multiple executions of the same process if a previous request is still running.
1 parent cac718e commit e5d8275

File tree

4 files changed

+804
-236
lines changed

4 files changed

+804
-236
lines changed

backend/api/market_recommender.py

Lines changed: 161 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,4 @@
1-
# This is the new version of the gecko market recommender which makes use of the /backend/utils/coingecko_api.py utility script.
2-
3-
"""
4-
Follow this high level structure when writing the new script.
5-
Only focus on one section at a time.
6-
I will instruct you which section we are working on.
7-
If I do not instruct you, ask me which section you are working on.
8-
This script will use synchronous code instead of asynchronous code in the previous version.
9-
10-
def fetch_market_data():
11-
# Minimal working data retrieval with ccxt
12-
pass
13-
14-
def score_assets(market_data):
15-
# Single simplified scoring method for all assets
16-
pass
17-
18-
def categorize_assets(scored_assets):
19-
# Clearly separate lists: to_list, to_delist, leverage_up, leverage_down
20-
pass
21-
22-
def main():
23-
data = fetch_market_data()
24-
scored = score_assets(data)
25-
categorized = categorize_assets(scored)
26-
# output simple DataFrames for Streamlit
27-
return categorized
28-
29-
"""
1+
# This is the new version of the market recommender which makes use of the coingecko API via the /backend/utils/coingecko_api.py utility script.
302

313
import logging
324
from typing import Dict, List
@@ -227,6 +199,10 @@ def fetch_driftpy_data(vat: Vat) -> Dict:
227199
base_oi_readable = base_oi / (10 ** base_decimals)
228200
oi_usd = base_oi_readable * oracle_price
229201

202+
# Calculate max leverage from margin ratio initial
203+
initial_margin_ratio = market.data.margin_ratio_initial / 10000
204+
max_leverage = int(1 / initial_margin_ratio) if initial_margin_ratio > 0 else 0
205+
230206
# Initialize symbol entry if not exists
231207
if normalized_symbol not in drift_markets:
232208
drift_markets[normalized_symbol] = {
@@ -236,7 +212,7 @@ def fetch_driftpy_data(vat: Vat) -> Dict:
236212
"drift_spot_markets": {},
237213
"drift_total_quote_volume_30d": 0.0,
238214
"drift_total_base_volume_30d": 0.0,
239-
"drift_max_leverage": float(market.data.margin_ratio_initial),
215+
"drift_max_leverage": max_leverage,
240216
"drift_open_interest": oi_usd,
241217
"drift_funding_rate_1h": float(market.data.amm.last_funding_rate) / 1e6 * 100
242218
}
@@ -361,7 +337,6 @@ def fetch_api_page(url: str, retries: int = 5) -> Dict:
361337

362338
if not DRIFT_DATA_API_AUTH:
363339
logger.error("DRIFT_DATA_API_AUTH environment variable not set")
364-
return {"success": False, "records": [], "meta": {"totalRecords": 0}}
365340

366341
last_request_time = getattr(fetch_api_page, 'last_request_time', 0)
367342

@@ -563,54 +538,153 @@ def calculate_market_volume(symbol: str) -> dict:
563538

564539
return drift_markets
565540

566-
def calculate_volume_score(volume_30d: float) -> float:
541+
def calculate_drift_volume_score(volume_30d: float) -> float:
567542
"""
568-
Calculate a score based on 30-day trading volume.
543+
Calculate a score based on Drift Protocol 30-day trading volume.
569544
570545
Args:
571546
volume_30d (float): 30-day trading volume in USD
572547
573548
Returns:
574-
float: Volume score between 0 and 50
549+
float: Volume score between 0 and 25
575550
"""
576-
# Score based on volume tiers (adjust thresholds as needed)
577-
if volume_30d >= 1_000_000_000: # $1B+
578-
return 50.0
579-
elif volume_30d >= 500_000_000: # $500M+
580-
return 40.0
581-
elif volume_30d >= 100_000_000: # $100M+
582-
return 30.0
583-
elif volume_30d >= 50_000_000: # $50M+
551+
# Score based on volume tiers according to the scoring breakdown
552+
if volume_30d >= 500_000_000: # $500M+
553+
return 25.0
554+
elif volume_30d >= 100_000_000: # $100M - $499M
584555
return 20.0
585-
elif volume_30d >= 10_000_000: # $10M+
556+
elif volume_30d >= 25_000_000: # $25M - $99M
557+
return 15.0
558+
elif volume_30d >= 1_000_000: # $1M - $24M
586559
return 10.0
587-
else:
588-
return max(0.0, min(10.0, volume_30d / 1_000_000)) # Linear score up to $10M
560+
elif volume_30d >= 100_000: # $100K - $999K
561+
return 5.0
562+
else: # < $100K
563+
return 0.0
589564

590-
def calculate_leverage_score(max_leverage: float) -> float:
565+
def calculate_open_interest_score(open_interest: float) -> float:
591566
"""
592-
Calculate a score based on maximum leverage offered.
567+
Calculate a score based on Drift Protocol open interest.
593568
594569
Args:
595-
max_leverage (float): Maximum leverage offered
570+
open_interest (float): Open interest in USD
596571
597572
Returns:
598-
float: Leverage score between 0 and 50
573+
float: Open interest score between 0 and 25
599574
"""
600-
# Score based on leverage tiers
601-
if max_leverage >= 20:
602-
return 50.0
603-
elif max_leverage >= 15:
575+
# Score based on OI tiers according to the scoring breakdown
576+
if open_interest >= 5_000_000: # $5M+
577+
return 25.0
578+
elif open_interest >= 1_000_000: # $1M - $4.9M
579+
return 20.0
580+
elif open_interest >= 250_000: # $250K - $999K
581+
return 15.0
582+
elif open_interest >= 50_000: # $50K - $249K
583+
return 10.0
584+
elif open_interest >= 5_000: # $5K - $49K
585+
return 5.0
586+
else: # < $5K
587+
return 0.0
588+
589+
def calculate_global_volume_score(daily_volume: float) -> float:
590+
"""
591+
Calculate a score based on global trading volume from CoinGecko.
592+
593+
Args:
594+
daily_volume (float): Daily trading volume in USD
595+
596+
Returns:
597+
float: Global volume score between 0 and 40
598+
"""
599+
# Score based on global volume tiers according to the scoring breakdown
600+
if daily_volume >= 500_000_000: # $500M+
604601
return 40.0
605-
elif max_leverage >= 10:
602+
elif daily_volume >= 250_000_000: # $250M - $499M
606603
return 30.0
607-
elif max_leverage >= 5:
604+
elif daily_volume >= 100_000_000: # $100M - $249M
608605
return 20.0
609-
elif max_leverage > 0:
606+
elif daily_volume >= 25_000_000: # $25M - $99M
610607
return 10.0
611-
else:
608+
elif daily_volume >= 5_000_000: # $5M - $24M
609+
return 5.0
610+
else: # < $5M
611+
return 0.0
612+
613+
def calculate_fdv_score(fdv: float) -> float:
614+
"""
615+
Calculate a score based on Fully Diluted Valuation (FDV).
616+
617+
Args:
618+
fdv (float): Fully Diluted Valuation in USD
619+
620+
Returns:
621+
float: FDV score between 0 and 10
622+
"""
623+
# Score based on FDV tiers according to the scoring breakdown
624+
if fdv >= 10_000_000_000: # $10B+
625+
return 10.0
626+
elif fdv >= 1_000_000_000: # $1B - $9.9B
627+
return 8.0
628+
elif fdv >= 500_000_000: # $500M - $999M
629+
return 6.0
630+
elif fdv >= 100_000_000: # $100M - $499M
631+
return 2.0
632+
else: # < $100M
612633
return 0.0
613634

635+
def get_market_recommendation(total_score: float, current_leverage: float) -> str:
636+
"""
637+
Determine market recommendation based on total score and current leverage.
638+
639+
Args:
640+
total_score (float): Total score from all criteria (0-100)
641+
current_leverage (float): Current maximum leverage offered
642+
643+
Returns:
644+
str: Recommendation (List, Increase Leverage, Decrease Leverage, Delist, or No Action)
645+
"""
646+
# Define upper and lower bound thresholds for different leverage levels
647+
SCORE_UB = {
648+
0: 45, # Unlisted
649+
2: float('inf'), # No upper bound for 2x (cannot increase further)
650+
4: 75,
651+
5: 80,
652+
10: 90,
653+
20: 95
654+
}
655+
656+
SCORE_LB = {
657+
0: 0, # Not applicable for unlisted
658+
2: 40, # Delist threshold for 2x
659+
4: 50,
660+
5: 60,
661+
10: 70,
662+
20: 75
663+
}
664+
665+
# Find the closest leverage level for thresholds
666+
leverage_levels = sorted(SCORE_UB.keys())
667+
closest_leverage = leverage_levels[0]
668+
669+
for level in leverage_levels:
670+
if level <= current_leverage:
671+
closest_leverage = level
672+
673+
# Apply decision logic
674+
if current_leverage == 0: # Unlisted
675+
if total_score >= SCORE_UB[0]:
676+
return "List"
677+
else:
678+
return "Do Nothing"
679+
elif current_leverage == 2 and total_score <= SCORE_LB[2]:
680+
return "Delist"
681+
elif total_score >= SCORE_UB.get(closest_leverage, float('inf')):
682+
return "Increase Leverage"
683+
elif total_score <= SCORE_LB.get(closest_leverage, 0) and closest_leverage > 2:
684+
return "Decrease Leverage"
685+
else:
686+
return "No Action"
687+
614688
def score_assets(assets: List[Dict], drift_data: Dict) -> List[Dict]:
615689
"""
616690
Score assets based on Drift market data and other metrics.
@@ -630,23 +704,41 @@ def score_assets(assets: List[Dict], drift_data: Dict) -> List[Dict]:
630704
# Get Drift market data if available
631705
market_info = drift_data.get(symbol, {})
632706

633-
# Calculate total volumes from all markets
634-
total_quote_volume = market_info.get('drift_total_quote_volume_30d', 0.0)
635-
total_base_volume = market_info.get('drift_total_base_volume_30d', 0.0)
636-
637-
# Calculate scores using helper functions
638-
volume_score = calculate_volume_score(total_quote_volume)
639-
leverage_score = calculate_leverage_score(market_info.get('drift_max_leverage', 0.0))
707+
# Get raw metrics
708+
drift_volume_30d = market_info.get('drift_total_quote_volume_30d', 0.0)
709+
drift_open_interest = market_info.get('drift_open_interest', 0.0)
710+
global_daily_volume = asset.get('coingecko_data', {}).get('coingecko_total_volume_24h', 0.0)
711+
fdv = asset.get('coingecko_data', {}).get('coingecko_fully_diluted_valuation', 0.0)
712+
current_leverage = market_info.get('drift_max_leverage', 0.0)
640713

641-
# Combine scores (equal weighting for now)
642-
total_score = volume_score + leverage_score
714+
# Calculate component scores
715+
drift_volume_score = calculate_drift_volume_score(drift_volume_30d)
716+
open_interest_score = calculate_open_interest_score(drift_open_interest)
717+
global_volume_score = calculate_global_volume_score(global_daily_volume)
718+
fdv_score = calculate_fdv_score(fdv)
719+
720+
# Calculate total score
721+
total_score = drift_volume_score + open_interest_score + global_volume_score + fdv_score
722+
723+
# Get recommendation
724+
recommendation = get_market_recommendation(total_score, current_leverage)
643725

644726
# Create scored asset dictionary with nested drift_data
645727
scored_asset = {
646728
**asset, # Include all original asset data
647-
'volume_score': volume_score,
648-
'leverage_score': leverage_score,
649-
'total_score': total_score
729+
'drift_volume_score': drift_volume_score,
730+
'open_interest_score': open_interest_score,
731+
'global_volume_score': global_volume_score,
732+
'fdv_score': fdv_score,
733+
'total_score': total_score,
734+
'recommendation': recommendation,
735+
'raw_metrics': {
736+
'drift_volume_30d': drift_volume_30d,
737+
'drift_open_interest': drift_open_interest,
738+
'global_daily_volume': global_daily_volume,
739+
'fdv': fdv,
740+
'current_max_leverage': current_leverage
741+
}
650742
}
651743

652744
# If there's no drift data, initialize with default structure

0 commit comments

Comments
 (0)