use serde::{Deserialize, Serialize};
pub const STATE_DIM: usize = 24;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TradeState {
pub pool_age_secs: f64,
pub initial_liquidity_sol: f64,
pub price_change_pct: f64,
pub volume_5min_sol: f64,
pub buy_sell_ratio: f64,
pub lp_burned: bool,
pub mint_renounced: bool,
pub current_pnl_pct: f64,
pub position_age_secs: f64,
pub daily_pnl_sol: f64,
pub consecutive_wins: i32,
pub consecutive_losses: i32,
pub sol_balance_sol: f64,
pub regime: i32,
pub volatility: f64,
pub spread_pct: f64,
pub time_of_day_norm: f64,
pub open_positions: i32,
pub peak_pnl_pct: f64,
pub pool_score_norm: f64,
pub deployer_rug_rate: f64,
pub volume_velocity: f64,
pub price_velocity: f64,
pub price_acceleration: f64,
}
impl TradeState {
pub fn to_vec(&self) -> Vec<f64> {
vec![
(self.pool_age_secs / 3_600.0).min(1.0),
(self.initial_liquidity_sol / 100.0).min(1.0),
self.price_change_pct.clamp(-1.0, 3.0) / 3.0,
(self.volume_5min_sol / 50.0).min(1.0),
(self.buy_sell_ratio / 5.0).min(1.0),
if self.lp_burned { 1.0 } else { 0.0 },
if self.mint_renounced { 1.0 } else { 0.0 },
self.current_pnl_pct.clamp(-1.0, 2.0) / 2.0 + 0.5,
(self.position_age_secs / 3_600.0).min(1.0),
self.daily_pnl_sol.clamp(-2.0, 2.0) / 2.0 + 0.5,
(self.consecutive_wins as f64 / 10.0).min(1.0),
(self.consecutive_losses as f64 / 10.0).min(1.0),
(self.sol_balance_sol / 10.0).min(1.0),
(self.regime as f64 + 1.0) / 2.0,
self.volatility.clamp(0.0, 1.0),
(self.spread_pct / 0.1).min(1.0),
self.time_of_day_norm.clamp(0.0, 1.0),
(self.open_positions as f64 / 5.0).min(1.0),
self.peak_pnl_pct.clamp(0.0, 5.0) / 5.0,
self.pool_score_norm.clamp(0.0, 1.0),
self.deployer_rug_rate.clamp(0.0, 1.0),
self.volume_velocity.clamp(-1.0, 1.0) * 0.5 + 0.5,
self.price_velocity.clamp(-1.0, 1.0) * 0.5 + 0.5,
self.price_acceleration.clamp(-1.0, 1.0) * 0.5 + 0.5,
]
}
pub fn from_trade_fields(
pnl_pct: f64,
position_age_secs: f64,
daily_pnl_sol: f64,
consecutive_wins: i32,
consecutive_losses: i32,
sol_balance_sol: f64,
open_positions: i32,
) -> Self {
use chrono::Timelike;
let hour = chrono::Utc::now().hour() as f64;
Self {
current_pnl_pct: pnl_pct,
position_age_secs,
daily_pnl_sol,
consecutive_wins,
consecutive_losses,
sol_balance_sol,
open_positions,
time_of_day_norm: hour / 24.0,
deployer_rug_rate: 0.5, ..Default::default()
}
}
pub fn from_live_fields(
pnl_pct: f64,
peak_pnl_pct: f64,
position_age_secs: f64,
daily_pnl_sol: f64,
consecutive_wins: i32,
consecutive_losses: i32,
sol_balance_sol: f64,
open_positions: i32,
pool_score_norm: f64,
deployer_rug_rate: f64,
volume_velocity: f64,
price_velocity: f64,
price_acceleration: f64,
) -> Self {
use chrono::Timelike;
let hour = chrono::Utc::now().hour() as f64;
Self {
current_pnl_pct: pnl_pct,
peak_pnl_pct,
position_age_secs,
daily_pnl_sol,
consecutive_wins,
consecutive_losses,
sol_balance_sol,
open_positions,
pool_score_norm,
deployer_rug_rate,
volume_velocity,
price_velocity,
price_acceleration,
time_of_day_norm: hour / 24.0,
..Default::default()
}
}
}