rust-okx 0.6.4

Async Rust client for the OKX v5 REST API
Documentation
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, Cell, Paragraph, Row, Table};

use crate::app::App;

pub fn render(f: &mut Frame, area: Rect, app: &App) {
    let chunks = Layout::vertical([Constraint::Length(7), Constraint::Fill(1)]).split(area);
    render_summary(f, chunks[0], app);
    render_tables(f, chunks[1], app);
}

fn render_summary(f: &mut Frame, area: Rect, app: &App) {
    let mut lines = Vec::new();
    lines.push(Line::from(vec![
        Span::raw("  权限: "),
        Span::styled(
            if app.perm.is_empty() {
                "unknown"
            } else {
                app.perm.as_str()
            },
            Style::default().fg(Color::Cyan),
        ),
        Span::raw("    模式: "),
        Span::styled(
            app.config.mode_label(),
            Style::default().fg(if app.config.demo {
                Color::Yellow
            } else {
                Color::Red
            }),
        ),
        Span::raw("    交易: "),
        Span::styled(
            if app.config.trade_enabled {
                "enabled"
            } else {
                "read-only"
            },
            Style::default().fg(if app.config.trade_enabled {
                Color::Green
            } else {
                Color::DarkGray
            }),
        ),
    ]));
    lines.push(Line::raw(""));
    lines.push(Line::from(format!(
        "  非零余额: {}    持仓: {}    挂单: {}",
        app.balance_details().count(),
        app.positions.len(),
        app.orders.len()
    )));
    lines.push(Line::styled(
        "  提醒:Trade 页只有 --trade-enabled 时才允许真实交易动作。",
        Style::default().fg(Color::DarkGray),
    ));

    f.render_widget(
        Paragraph::new(lines).block(Block::default().borders(Borders::ALL).title(" 账户概览 ")),
        area,
    );
}

fn render_tables(f: &mut Frame, area: Rect, app: &App) {
    let chunks =
        Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]).split(area);
    render_balances(f, chunks[0], app);
    render_positions(f, chunks[1], app);
}

fn render_balances(f: &mut Frame, area: Rect, app: &App) {
    let rows: Vec<Row> = app
        .balance_details()
        .map(|d| {
            Row::new(vec![
                Cell::from(d.ccy.as_str()),
                Cell::from(d.eq.as_str()),
                Cell::from(d.avail_bal.as_str()),
                Cell::from(d.frozen_bal.as_str()),
            ])
        })
        .collect();
    let table = Table::new(
        rows,
        [
            Constraint::Length(8),
            Constraint::Percentage(32),
            Constraint::Percentage(32),
            Constraint::Percentage(28),
        ],
    )
    .header(Row::new(["币种", "权益", "可用", "冻结"]).style(Style::default().fg(Color::Cyan)))
    .block(Block::default().borders(Borders::ALL).title(" 余额 "));
    f.render_widget(table, area);
}

fn render_positions(f: &mut Frame, area: Rect, app: &App) {
    let rows: Vec<Row> = app
        .positions
        .iter()
        .map(|p| {
            Row::new(vec![
                Cell::from(p.inst_id.as_str()),
                Cell::from(p.pos_side.as_str()),
                Cell::from(p.pos.as_str()),
                Cell::from(p.avg_px.as_str()),
                Cell::from(p.upl.as_str()),
            ])
        })
        .collect();
    let table = Table::new(
        rows,
        [
            Constraint::Percentage(32),
            Constraint::Length(8),
            Constraint::Percentage(20),
            Constraint::Percentage(20),
            Constraint::Percentage(20),
        ],
    )
    .header(
        Row::new(["合约", "方向", "数量", "均价", "未实现"])
            .style(Style::default().fg(Color::Cyan)),
    )
    .block(Block::default().borders(Borders::ALL).title(" 持仓 "));
    f.render_widget(table, area);
}