use anyhow::Result;
use clmm_lp_data::prelude::*;
use clmm_lp_domain::entities::token::Token;
use rust_decimal::Decimal;
use std::path::PathBuf;
use tracing::info;
#[derive(Debug, Clone)]
pub struct DataArgs {
pub action: DataAction,
}
#[derive(Debug, Clone)]
pub enum DataAction {
Fetch(FetchArgs),
Export(ExportArgs),
CacheStatus,
ClearCache,
}
#[derive(Debug, Clone)]
pub struct FetchArgs {
pub symbol_a: String,
pub mint_a: String,
pub symbol_b: String,
pub mint_b: String,
pub hours: u64,
pub resolution_minutes: u64,
}
#[derive(Debug, Clone)]
pub struct ExportArgs {
pub symbol_a: String,
pub mint_a: String,
pub symbol_b: String,
pub mint_b: String,
pub hours: u64,
pub output: PathBuf,
pub format: ExportFormat,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum ExportFormat {
#[default]
Csv,
Json,
}
impl Default for FetchArgs {
fn default() -> Self {
Self {
symbol_a: "SOL".to_string(),
mint_a: "So11111111111111111111111111111111111111112".to_string(),
symbol_b: "USDC".to_string(),
mint_b: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
hours: 24,
resolution_minutes: 60,
}
}
}
pub async fn run_data(args: DataArgs) -> Result<()> {
match args.action {
DataAction::Fetch(fetch_args) => run_fetch(fetch_args).await,
DataAction::Export(export_args) => run_export(export_args).await,
DataAction::CacheStatus => run_cache_status().await,
DataAction::ClearCache => run_clear_cache().await,
}
}
async fn run_fetch(args: FetchArgs) -> Result<()> {
info!(
"Fetching {}/{} data for {} hours",
args.symbol_a, args.symbol_b, args.hours
);
let api_key =
std::env::var("BIRDEYE_API_KEY").map_err(|_| anyhow::anyhow!("BIRDEYE_API_KEY not set"))?;
let provider = BirdeyeProvider::new(api_key);
let token_a = Token::new(&args.mint_a, &args.symbol_a, 9, &args.symbol_a);
let token_b = Token::new(&args.mint_b, &args.symbol_b, 6, &args.symbol_b);
let end_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
let start_time = end_time - (args.hours * 3600);
let resolution = args.resolution_minutes * 60;
let candles = provider
.get_price_history(&token_a, &token_b, start_time, end_time, resolution)
.await?;
println!("\nđ Data Fetch Summary");
println!("âââââââââââââââââââââââââââââââââââââââ");
println!("Pair: {}/{}", args.symbol_a, args.symbol_b);
println!("Period: {} hours", args.hours);
println!("Resolution: {} minutes", args.resolution_minutes);
println!("Candles: {}", candles.len());
if !candles.is_empty() {
let first = &candles[0];
let last = candles.last().unwrap();
println!("\nđ Price Summary");
println!("âââââââââââââââââââââââââââââââââââââââ");
println!("First Price: {}", first.close.value);
println!("Last Price: {}", last.close.value);
let high = candles.iter().map(|c| c.high.value).max().unwrap();
let low = candles.iter().map(|c| c.low.value).min().unwrap();
println!("High: {}", high);
println!("Low: {}", low);
let change =
(last.close.value - first.close.value) / first.close.value * Decimal::from(100);
println!("Change: {:.2}%", change);
}
Ok(())
}
async fn run_export(args: ExportArgs) -> Result<()> {
info!(
"Exporting {}/{} data to {:?}",
args.symbol_a, args.symbol_b, args.output
);
let api_key =
std::env::var("BIRDEYE_API_KEY").map_err(|_| anyhow::anyhow!("BIRDEYE_API_KEY not set"))?;
let provider = BirdeyeProvider::new(api_key);
let token_a = Token::new(&args.mint_a, &args.symbol_a, 9, &args.symbol_a);
let token_b = Token::new(&args.mint_b, &args.symbol_b, 6, &args.symbol_b);
let end_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
let start_time = end_time - (args.hours * 3600);
let candles = provider
.get_price_history(&token_a, &token_b, start_time, end_time, 3600)
.await?;
match args.format {
ExportFormat::Csv => {
write_candles_to_csv(&candles, &args.output)?;
println!("â
Exported {} candles to {:?}", candles.len(), args.output);
}
ExportFormat::Json => {
let json = serde_json::to_string_pretty(&candles)?;
std::fs::write(&args.output, json)?;
println!("â
Exported {} candles to {:?}", candles.len(), args.output);
}
}
Ok(())
}
async fn run_cache_status() -> Result<()> {
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("clmm-lp");
println!("\nđž Cache Status");
println!("âââââââââââââââââââââââââââââââââââââââ");
println!("Cache Directory: {:?}", cache_dir);
if cache_dir.exists() {
let mut total_size = 0u64;
let mut file_count = 0usize;
if let Ok(entries) = std::fs::read_dir(&cache_dir) {
for entry in entries.flatten() {
if let Ok(metadata) = entry.metadata() {
total_size += metadata.len();
file_count += 1;
}
}
}
println!("Files: {}", file_count);
println!("Total Size: {} KB", total_size / 1024);
} else {
println!("Status: No cache directory found");
}
Ok(())
}
async fn run_clear_cache() -> Result<()> {
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("clmm-lp");
if cache_dir.exists() {
std::fs::remove_dir_all(&cache_dir)?;
println!("â
Cache cleared");
} else {
println!("âšī¸ No cache to clear");
}
Ok(())
}