sandbox-quant 1.0.8

Exchange-truth trading core for Binance Spot and Futures
Documentation
use crate::app::bootstrap::BinanceMode;
use crate::command::recorder::{
    complete_recorder_input, parse_recorder_shell_input, recorder_help_text, RecorderCommand,
    RecorderShellInput,
};
use crate::record::coordination::RecorderCoordination;
use crate::recorder_app::runtime::{MarketDataRecorder, RecorderState};
use crate::terminal::app::{TerminalApp, TerminalEvent, TerminalMode};
use crate::terminal::completion::ShellCompletion;
use crate::ui::recorder_output::render_live_recorder_status;

pub struct RecorderTerminal {
    pub mode: BinanceMode,
    pub base_dir: String,
    pub recorder: MarketDataRecorder,
    pub coordination: RecorderCoordination,
    pub manual_symbols: Vec<String>,
}

impl RecorderTerminal {
    pub fn new(mode: BinanceMode, base_dir: impl Into<String>) -> Self {
        let base_dir = base_dir.into();
        Self {
            mode,
            recorder: MarketDataRecorder::new(base_dir.clone()),
            coordination: RecorderCoordination::new(base_dir.clone()),
            base_dir,
            manual_symbols: Vec::new(),
        }
    }

    fn sync_strategy_symbols(&mut self) -> Result<Vec<String>, String> {
        let strategy_symbols = self
            .coordination
            .strategy_symbols(self.mode)
            .map_err(|error| error.to_string())?;
        if self.recorder.status(self.mode).state == RecorderState::Running {
            self.recorder
                .update_strategy_symbols(self.mode, strategy_symbols.clone())
                .map_err(|error| error.to_string())?;
        }
        Ok(strategy_symbols)
    }
}

impl TerminalApp for RecorderTerminal {
    fn terminal_mode(&self) -> TerminalMode {
        TerminalMode::Line
    }

    fn intro_panel(&self) -> String {
        format!(
            "╭──────────────────────────────────────────────╮\n│ >_ Sandbox Quant Recorder (v{})              │\n│                                              │\n│ mode:      {:<18} /mode to change │\n│ base_dir:  {:<28} │\n╰──────────────────────────────────────────────╯",
            env!("CARGO_PKG_VERSION"),
            self.mode.as_str(),
            self.base_dir
        )
    }

    fn help_text(&self) -> String {
        recorder_help_text().to_string()
    }

    fn prompt(&self) -> String {
        format!("[recorder:{}] › ", self.mode.as_str())
    }

    fn complete(&self, line: &str) -> Vec<ShellCompletion> {
        complete_recorder_input(line)
    }

    fn execute_line(&mut self, line: &str) -> Result<TerminalEvent, String> {
        match parse_recorder_shell_input(line) {
            Ok(RecorderShellInput::Empty) => Ok(TerminalEvent::NoOutput),
            Ok(RecorderShellInput::Help) => Ok(TerminalEvent::Output(self.help_text())),
            Ok(RecorderShellInput::Exit) => Ok(TerminalEvent::Exit),
            Ok(RecorderShellInput::Mode(mode)) => {
                self.mode = mode;
                Ok(TerminalEvent::Output(format!(
                    "mode switched to {}",
                    self.mode.as_str()
                )))
            }
            Ok(RecorderShellInput::Command(command)) => match command {
                RecorderCommand::Start { symbols } => {
                    self.manual_symbols = symbols;
                    let strategy_symbols = self.sync_strategy_symbols()?;
                    let status = if self.recorder.status(self.mode).state == RecorderState::Running
                    {
                        self.recorder
                            .update_manual_symbols(self.mode, self.manual_symbols.clone())
                            .map_err(|error| error.to_string())?;
                        self.recorder.status(self.mode)
                    } else {
                        self.recorder
                            .start(self.mode, self.manual_symbols.clone(), strategy_symbols)
                            .map_err(|error| error.to_string())?
                    };
                    Ok(TerminalEvent::Output(render_live_recorder_status(
                        "record started",
                        &status,
                    )))
                }
                RecorderCommand::Status => {
                    let _ = self.sync_strategy_symbols()?;
                    let status = self.recorder.status(self.mode);
                    Ok(TerminalEvent::Output(render_live_recorder_status(
                        "record status",
                        &status,
                    )))
                }
                RecorderCommand::Stop => {
                    let status = self
                        .recorder
                        .stop(self.mode)
                        .map_err(|error| error.to_string())?;
                    Ok(TerminalEvent::Output(render_live_recorder_status(
                        "record stopped",
                        &status,
                    )))
                }
            },
            Err(error) => Err(error),
        }
    }
}