use crate::trade::builder::BuilderSetting;
use crate::trade::error::TradeError;
use crate::trade::orders::limit::limit_on;
use crate::trade::orders::{Order, PlaceOrder, Side, Tif};
use crate::trade::ticket::{Market, Refusal, at_least, positive, routed};
use crate::utils::rounding::MINIMUM_ORDER_VALUE;
use crate::utils::{Dec, Round};
pub const LEAST_LEVELS: u32 = 2;
pub const MOST_LEVELS: u32 = 50;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Scale {
pub side: Side,
pub size: Dec,
pub from: Dec,
pub to: Dec,
pub levels: u32,
pub tif: Tif,
pub reduce_only: bool,
}
impl Scale {
#[must_use]
pub const fn new(side: Side, size: Dec, from: Dec, to: Dec, levels: u32, tif: Tif) -> Scale {
Scale { side, size, from, to, levels, tif, reduce_only: false }
}
}
pub fn scale(market: &Market, intent: &Scale, builder: Option<&BuilderSetting>) -> Result<PlaceOrder, Refusal> {
if market.is_spot() && intent.reduce_only {
return Err(Refusal::Spot("reduce only"));
}
if !matches!(intent.tif, Tif::Gtc | Tif::Alo) {
return Err(Refusal::ScaleTif(intent.tif));
}
let levels = intent.levels;
if !(LEAST_LEVELS..=MOST_LEVELS).contains(&levels) {
return Err(Refusal::Levels(levels));
}
let grid = market.grid();
let total = positive(intent.size, "a size")?;
let from = positive(intent.from, "a from price")?;
let to = positive(intent.to, "a to price")?;
let count = Dec::from_int(i64::from(levels));
let raw = total.checked_div(count, 12, Round::Down).ok_or(Refusal::Trade(TradeError::NoSize(total)))?;
let each = grid.size(raw).ok_or(Refusal::Trade(TradeError::NoSize(raw)))?;
let span = to.checked_sub(from).ok_or(Refusal::Trade(TradeError::NoPrice(to)))?;
let step = span
.checked_div(Dec::from_int(i64::from(levels - 1)), 12, Round::Down)
.ok_or(Refusal::Trade(TradeError::NoPrice(to)))?;
let mut orders = Vec::new();
for at in 0..levels {
let raw = match at + 1 == levels {
true => to,
false => step
.checked_mul(Dec::from_int(i64::from(at)))
.and_then(|moved| from.checked_add(moved))
.ok_or(Refusal::Trade(TradeError::NoPrice(from)))?,
};
let price = limit_on(grid, raw, intent.side, "a level's price")?;
at_least(each, price, MINIMUM_ORDER_VALUE)?;
let order = Order::limit(market.asset(), intent.side, price, each, intent.tif);
orders.push(match intent.reduce_only {
true => order.reduce_only(),
false => order,
});
}
routed(PlaceOrder::new(orders), builder)
}
#[cfg(test)]
#[path = "tests/scale.rs"]
mod tests;