use anyhow::{Context, Result};
use percli_core::{NamedEngine, POS_SCALE};
use std::path::Path;
use crate::format::{self, OutputFormat};
use crate::StepAction;
pub fn run(state_path: &str, action: StepAction, fmt: OutputFormat) -> Result<()> {
let path = Path::new(state_path);
let mut state: SavedState = if path.exists() {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read state file: {state_path}"))?;
serde_json::from_str::<SavedState>(&content)
.with_context(|| format!("failed to parse state file: {state_path}"))?
} else {
let params = percli_core::ParamsConfig::default();
SavedState {
params,
oracle_price: 1000,
slot: 0,
funding_rate: 0,
accounts: std::collections::BTreeMap::new(),
deposits: Vec::new(),
trades: Vec::new(),
}
};
let risk_params = state.params.to_risk_params();
let mut engine = NamedEngine::new(risk_params, state.slot, state.oracle_price);
engine.set_funding_rate(state.funding_rate);
for dep in &state.deposits {
engine.deposit(&dep.account, dep.amount)?;
}
for trade in &state.trades {
engine.trade(
&trade.long,
&trade.short,
trade.size_q,
trade.price,
)?;
}
match &action {
StepAction::Deposit { account, amount } => {
engine.deposit(account, *amount)?;
state.deposits.push(DepositRecord {
account: account.clone(),
amount: *amount,
});
}
StepAction::Withdraw { account, amount } => {
engine.withdraw(account, *amount)?;
state.deposits.push(DepositRecord {
account: account.clone(),
amount: 0, });
}
StepAction::Trade { long, short, size, price } => {
let size_q = *size * POS_SCALE as i128;
engine.trade(long, short, size_q, *price)?;
state.trades.push(TradeRecord {
long: long.clone(),
short: short.clone(),
size_q,
price: *price,
});
}
StepAction::Crank { oracle, slot } => {
engine.crank(*oracle, *slot)?;
state.oracle_price = *oracle;
state.slot = *slot;
}
StepAction::Liquidate { account } => {
let did = engine.liquidate(account)?;
if !did {
eprintln!("warning: {account} was not liquidatable");
}
}
StepAction::SetOracle { price } => {
engine.set_oracle(*price);
state.oracle_price = *price;
}
StepAction::SetSlot { slot } => {
engine.set_slot(*slot);
state.slot = *slot;
}
}
let state_json = serde_json::to_string_pretty(&state)?;
std::fs::write(path, &state_json)
.with_context(|| format!("failed to write state file: {state_path}"))?;
let snap = engine.snapshot();
format::print_snapshot(&snap, fmt);
Ok(())
}
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct SavedState {
pub params: percli_core::ParamsConfig,
pub oracle_price: u64,
pub slot: u64,
pub funding_rate: i64,
pub accounts: std::collections::BTreeMap<String, u16>,
pub deposits: Vec<DepositRecord>,
pub trades: Vec<TradeRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DepositRecord {
pub account: String,
pub amount: u128,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TradeRecord {
pub long: String,
pub short: String,
pub size_q: i128,
pub price: u64,
}