eli-cli 0.2.4

Internal CLI library for the `market-search` binary. Use `cargo install market-search`.
Documentation
fn build_ibkr_connection_config(
    account: Option<String>,
    host: Option<String>,
    port: Option<u16>,
    client_id: Option<i32>,
    market_data_type: Option<i32>,
    timeout_secs: Option<u64>,
) -> eli_core::finance::IbkrConnectionConfig {
    eli_core::finance::IbkrConnectionConfig {
        account,
        host,
        port,
        client_id,
        market_data_type,
        timeout_secs,
    }
}

async fn cmd_finance_ibkr(args: FinanceIbkrArgs) -> Result<()> {
    if args.format.trim().to_ascii_lowercase() != "json" {
        anyhow::bail!("unsupported --format (only 'json' is implemented)");
    }

    let ibkr = build_ibkr_connection_config(
        args.account.clone(),
        args.host.clone(),
        args.port,
        args.client_id,
        args.market_data_type,
        args.timeout_secs,
    );

    let output =
        match args.command {
            FinanceIbkrCommandArg::Snapshot => {
                if args.tickers.is_empty() {
                    anyhow::bail!("--tickers is required for --command snapshot");
                }
                let req = eli_core::finance::SnapshotRequest {
                    tickers: args.tickers.clone(),
                    as_of: None,
                    provider: eli_core::finance::ProviderKind::Ibkr,
                    ibkr: Some(ibkr.clone()),
                };
                serde_json::to_value(eli_core::finance::fetch_snapshot(req).await?)?
            }
            FinanceIbkrCommandArg::Timeseries => {
                if args.tickers.is_empty() {
                    anyhow::bail!("--tickers is required for --command timeseries");
                }
                let range = eli_core::finance::Span::parse(&args.range)
                    .map_err(|e| anyhow::anyhow!(e))
                    .context("parse --range")?;
                let granularity = eli_core::finance::Span::parse(&args.granularity)
                    .map_err(|e| anyhow::anyhow!(e))
                    .context("parse --granularity")?;
                let cache_dir = default_finance_cache_dir()?;
                let req = eli_core::finance::TimeseriesRequest {
                    tickers: args.tickers.clone(),
                    range,
                    granularity,
                    as_of: None,
                    provider: eli_core::finance::ProviderKind::Ibkr,
                    max_points_per_ticker: None,
                    ibkr: Some(ibkr.clone()),
                };
                serde_json::to_value(eli_core::finance::fetch_timeseries(req, &cache_dir).await?)?
            }
            FinanceIbkrCommandArg::AccountSummary => {
                let value = eli_core::finance::invoke_ibkr_bridge(
                    "account_summary",
                    json!({
                        "account": args.account,
                        "tags": args.tags,
                    }),
                    Some(&ibkr),
                )
                .await?;
                value
            }
            FinanceIbkrCommandArg::Positions => {
                let value = eli_core::finance::invoke_ibkr_bridge(
                    "positions",
                    json!({
                        "account": args.account,
                    }),
                    Some(&ibkr),
                )
                .await?;
                value
            }
            FinanceIbkrCommandArg::Portfolio => {
                let value = eli_core::finance::invoke_ibkr_bridge(
                    "portfolio",
                    json!({
                        "account": args.account,
                    }),
                    Some(&ibkr),
                )
                .await?;
                value
            }
            FinanceIbkrCommandArg::OpenOrders => {
                let value =
                    eli_core::finance::invoke_ibkr_bridge("open_orders", json!({}), Some(&ibkr))
                        .await?;
                value
            }
            FinanceIbkrCommandArg::PlaceOrder => {
                let symbol = args.symbol.clone().ok_or_else(|| {
                    anyhow::anyhow!("--symbol is required for --command place-order")
                })?;
                let side = args.side.clone().ok_or_else(|| {
                    anyhow::anyhow!("--side is required for --command place-order")
                })?;
                let quantity = args.quantity.ok_or_else(|| {
                    anyhow::anyhow!("--quantity is required for --command place-order")
                })?;
                let value = eli_core::finance::invoke_ibkr_bridge(
                    "place_order",
                    json!({
                        "account": args.account,
                        "side": side,
                        "order_type": args.order_type.unwrap_or_else(|| "MKT".to_string()),
                        "quantity": quantity,
                        "limit_price": args.limit_price,
                        "stop_price": args.stop_price,
                        "tif": args.tif.unwrap_or_else(|| "DAY".to_string()),
                        "contract": {
                            "symbol": symbol,
                            "sec_type": args.sec_type.unwrap_or_else(|| "STK".to_string()),
                            "exchange": args.exchange.unwrap_or_else(|| "SMART".to_string()),
                            "primary_exchange": args.primary_exchange,
                            "currency": args.currency.unwrap_or_else(|| "USD".to_string()),
                            "expiry": args.expiry,
                            "strike": args.strike,
                            "right": args.right,
                            "multiplier": args.multiplier,
                            "trading_class": args.trading_class,
                        }
                    }),
                    Some(&ibkr),
                )
                .await?;
                value
            }
            FinanceIbkrCommandArg::CancelOrder => {
                let order_id = args.order_id.ok_or_else(|| {
                    anyhow::anyhow!("--order-id is required for --command cancel-order")
                })?;
                let value = eli_core::finance::invoke_ibkr_bridge(
                    "cancel_order",
                    json!({
                        "order_id": order_id,
                    }),
                    Some(&ibkr),
                )
                .await?;
                value
            }
        };

    if let Some(out_path) = args.out {
        let wr = write_json_out_with_meta(
            out_path,
            &output,
            "finance.ibkr",
            &[format!("command={:?}", args.command)],
        )?;
        println!(
            "{{\"ok\":true,\"path\":{},\"meta_path\":{}}}",
            serde_json::to_string(&wr.out_path.display().to_string())
                .unwrap_or_else(|_| "\"\"".to_string()),
            serde_json::to_string(&wr.meta_path.display().to_string())
                .unwrap_or_else(|_| "\"\"".to_string()),
        );
        return Ok(());
    }

    println!("{}", serde_json::to_string_pretty(&output)?);
    Ok(())
}