This commit is contained in:
2026-08-07 09:25:51 +02:00
parent 6757452264
commit c1f591b940
8 changed files with 15 additions and 204 deletions
+74
View File
@@ -0,0 +1,74 @@
use std::{collections::HashMap, path::PathBuf};
use anyhow::Context;
use serde::{Deserialize, Serialize};
pub fn get_accounts_path() -> anyhow::Result<PathBuf> {
Ok(std::env::home_dir()
.context("Failed to get home dir")?
.join(".config")
.join("sol-hunter")
.join("accounts.json"))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Account {
#[serde(rename = "publicKey")]
pub public_key: String,
#[serde(rename = "privateKey")]
pub private_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountManager {
#[serde(rename = "apiKey")]
pub api_key: String,
pub active: String,
pub accounts: HashMap<String, Account>,
}
impl AccountManager {
pub async fn get() -> anyhow::Result<Self> {
let path = get_accounts_path()?;
if !path.exists() {
let default = Self::new().await?;
tokio::fs::create_dir_all(path.parent().unwrap()).await?;
tokio::fs::write(&path, default.to_string()?).await?;
println!("Please open {path:?} and edit the data accordingly");
return Ok(default);
}
let output = tokio::fs::read_to_string(path).await?;
Self::from_str(&output)
}
pub async fn new() -> anyhow::Result<Self> {
let mut accounts = HashMap::new();
accounts.insert(
"default".to_string(),
Account {
public_key: String::new(),
private_key: String::new(),
},
);
Ok(Self {
active: "default".to_string(),
api_key: String::new(),
accounts,
})
}
pub fn from_str(s: &str) -> anyhow::Result<Self> {
serde_json::from_str(s).context("Failed to parse accounts")
}
pub fn to_string(&self) -> anyhow::Result<String> {
serde_json::to_string(self).context("Failed to serialize accounts")
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod account;
pub mod tradelog;
+70
View File
@@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExitReason {
TakeProfit,
StopLoss,
TrailingStop,
MomentumStalled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeLog {
pub mint: String,
pub opened_at: chrono::DateTime<chrono::Utc>,
pub closed_at: Option<chrono::DateTime<chrono::Utc>>,
pub duration_secs: Option<i64>,
pub entry_price_sol: f64,
pub exit_price_sol: Option<f64>,
pub pnl_percent: Option<f64>,
pub exit_reason: Option<ExitReason>,
pub unique_buyers: usize,
pub net_sol_flow: f64,
pub curve_sol: f64,
}
impl TradeLog {
pub fn new(
mint: String,
entry_price_sol: f64,
unique_buyers: usize,
net_sol_flow: f64,
curve_sol: f64,
) -> Self {
Self {
mint,
opened_at: chrono::Utc::now(),
closed_at: None,
entry_price_sol,
exit_price_sol: None,
pnl_percent: None,
duration_secs: None,
exit_reason: None,
unique_buyers,
net_sol_flow,
curve_sol,
}
}
pub fn close(&mut self, exit_price_sol: f64, reason: ExitReason) {
let now = chrono::Utc::now();
let pnl = ((exit_price_sol - self.entry_price_sol) / self.entry_price_sol) * 100.0;
self.exit_price_sol = Some(exit_price_sol);
self.pnl_percent = Some(pnl);
self.closed_at = Some(now);
self.duration_secs = Some(now.signed_duration_since(self.opened_at).num_seconds());
self.exit_reason = Some(reason);
}
}