use rust_decimal::Decimal;
use crate::types::{
Cursor, Interval, MarginMode, Market, OrderType, Side, Size, TimeInForce, Timestamp,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandleRequest {
pub market: Market,
pub interval: Interval,
pub from: Option<Timestamp>,
pub to: Option<Timestamp>,
pub limit: Option<u32>,
}
impl CandleRequest {
pub fn new(market: Market, interval: Interval) -> Self {
Self {
market,
interval,
from: None,
to: None,
limit: None,
}
}
#[must_use]
pub fn from(mut self, from: Timestamp) -> Self {
self.from = Some(from);
self
}
#[must_use]
pub fn to(mut self, to: Timestamp) -> Self {
self.to = Some(to);
self
}
#[must_use]
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OrderRequest {
pub market: Market,
pub side: Side,
pub order_type: OrderType,
pub size: Size,
pub price: Option<Decimal>,
pub time_in_force: Option<TimeInForce>,
pub reduce_only: bool,
}
impl OrderRequest {
pub fn market(market: Market, side: Side, size: Size) -> Self {
Self {
market,
side,
order_type: OrderType::Market,
size,
price: None,
time_in_force: None,
reduce_only: false,
}
}
pub fn limit(market: Market, side: Side, size: Size, price: Decimal) -> Self {
Self {
market,
side,
order_type: OrderType::Limit,
size,
price: Some(price),
time_in_force: None,
reduce_only: false,
}
}
#[must_use]
pub fn time_in_force(mut self, time_in_force: TimeInForce) -> Self {
self.time_in_force = Some(time_in_force);
self
}
#[must_use]
pub fn reduce_only(mut self) -> Self {
self.reduce_only = true;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HistoryRequest {
pub market: Market,
pub from: Option<Timestamp>,
pub to: Option<Timestamp>,
pub cursor: Option<Cursor>,
pub limit: Option<u32>,
}
impl HistoryRequest {
pub fn new(market: Market) -> Self {
Self {
market,
from: None,
to: None,
cursor: None,
limit: None,
}
}
#[must_use]
pub fn from(mut self, from: Timestamp) -> Self {
self.from = Some(from);
self
}
#[must_use]
pub fn to(mut self, to: Timestamp) -> Self {
self.to = Some(to);
self
}
#[must_use]
pub fn cursor(mut self, cursor: Cursor) -> Self {
self.cursor = Some(cursor);
self
}
#[must_use]
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MarginRequest {
pub market: Market,
pub leverage: Option<Decimal>,
pub margin_mode: Option<MarginMode>,
}
impl MarginRequest {
pub fn new(market: Market) -> Self {
Self {
market,
leverage: None,
margin_mode: None,
}
}
#[must_use]
pub fn leverage(mut self, leverage: Decimal) -> Self {
self.leverage = Some(leverage);
self
}
#[must_use]
pub fn margin_mode(mut self, margin_mode: MarginMode) -> Self {
self.margin_mode = Some(margin_mode);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Exchange;
fn btc_krw() -> Market {
Market::spot(Exchange::Upbit, "BTC", "KRW")
}
#[test]
fn market_orders_carry_no_price_and_limit_orders_always_do() {
let market_order =
OrderRequest::market(btc_krw(), Side::Buy, Size::Quote(Decimal::from(10_000)));
let limit_order = OrderRequest::limit(
btc_krw(),
Side::Sell,
Size::Base(Decimal::new(1, 2)),
Decimal::from(100_000_000),
);
assert_eq!(market_order.order_type, OrderType::Market);
assert_eq!(market_order.price, None);
assert_eq!(limit_order.order_type, OrderType::Limit);
assert_eq!(limit_order.price, Some(Decimal::from(100_000_000)));
}
#[test]
fn orders_are_not_reduce_only_unless_asked() {
let plain = OrderRequest::market(btc_krw(), Side::Buy, Size::Base(Decimal::ONE));
let reducing = plain.clone().reduce_only();
assert!(!plain.reduce_only);
assert!(reducing.reduce_only);
}
#[test]
fn builders_leave_unset_fields_alone() {
let request = CandleRequest::new(btc_krw(), Interval::Min1).limit(200);
assert_eq!(request.limit, Some(200));
assert_eq!(request.from, None);
assert_eq!(request.to, None);
}
#[test]
fn a_history_page_resumes_from_the_previous_cursor() {
let first = HistoryRequest::new(btc_krw());
assert!(first.cursor.is_none());
let second = HistoryRequest::new(btc_krw()).cursor(Cursor("page-2".to_string()));
assert_eq!(second.cursor.unwrap().as_str(), "page-2");
}
#[test]
fn a_margin_request_can_set_either_field_independently() {
let leverage_only = MarginRequest::new(btc_krw()).leverage(Decimal::from(10));
let mode_only = MarginRequest::new(btc_krw()).margin_mode(MarginMode::Isolated);
assert_eq!(leverage_only.leverage, Some(Decimal::from(10)));
assert_eq!(leverage_only.margin_mode, None);
assert_eq!(mode_only.leverage, None);
assert_eq!(mode_only.margin_mode, Some(MarginMode::Isolated));
}
}