Skip to content

Commit

Permalink
Optimize the min volume settings. (#898)
Browse files Browse the repository at this point in the history
#873 #849

* Reduce min trading vol in certain cases.
Add RPC call to display it.

* Fix compilation.
  • Loading branch information
artemii235 authored Apr 12, 2021
1 parent 7cb5800 commit dd24de2
Show file tree
Hide file tree
Showing 15 changed files with 127 additions and 38 deletions.
9 changes: 9 additions & 0 deletions etomic_build/client/min_trading_vol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
source userpass
curl --url "http://127.0.0.1:7783" --data '
{
"userpass":"'$userpass'",
"method":"min_trading_vol",
"coin": "'$1'"
}
'
6 changes: 6 additions & 0 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub use rlp;

mod web3_transport;
use self::web3_transport::Web3Transport;
use common::mm_number::MmNumber;

#[cfg(test)] mod eth_tests;
#[cfg(target_arch = "wasm32")] mod eth_wasm_tests;
Expand Down Expand Up @@ -1084,6 +1085,11 @@ impl MarketCoinOps for EthCoin {
fn display_priv_key(&self) -> String { format!("{:#02x}", self.key_pair.secret()) }

fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) }

fn min_trading_vol(&self) -> MmNumber {
let pow = self.decimals / 3;
MmNumber::from(1) / MmNumber::from(10u64.pow(pow as u32))
}
}

pub fn signed_eth_tx_from_bytes(bytes: &[u8]) -> Result<SignedEthTx, String> {
Expand Down
3 changes: 3 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ pub trait MarketCoinOps {

/// Get the minimum amount to send.
fn min_tx_amount(&self) -> BigDecimal;

/// Get the minimum amount to trade.
fn min_trading_vol(&self) -> MmNumber;
}

#[derive(Deserialize)]
Expand Down
6 changes: 6 additions & 0 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use common::executor::Timer;
use common::jsonrpc_client::{JsonRpcClient, JsonRpcError, JsonRpcRequest, RpcRes};
use common::log::{error, warn};
use common::mm_ctx::MmArc;
use common::mm_number::MmNumber;
use common::{block_on, Traceable};
use ethabi::{Function, Token};
use ethereum_types::{H160, U256};
Expand Down Expand Up @@ -914,6 +915,11 @@ impl MarketCoinOps for Qrc20Coin {
fn display_priv_key(&self) -> String { utxo_common::display_priv_key(&self.utxo) }

fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) }

fn min_trading_vol(&self) -> MmNumber {
let pow = self.utxo.decimals / 3;
MmNumber::from(1) / MmNumber::from(10u64.pow(pow as u32))
}
}

impl MmCoin for Qrc20Coin {
Expand Down
3 changes: 3 additions & 0 deletions mm2src/coins/test_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{FeeApproxStage, FoundSwapTxSpend, TradePreimageError, TradePreimageV
WithdrawRequest};
use bigdecimal::BigDecimal;
use common::mm_ctx::MmArc;
use common::mm_number::MmNumber;
use futures01::Future;
use mocktopus::macros::*;
use rpc::v1::types::Bytes as BytesJson;
Expand Down Expand Up @@ -68,6 +69,8 @@ impl MarketCoinOps for TestCoin {
fn display_priv_key(&self) -> String { unimplemented!() }

fn min_tx_amount(&self) -> BigDecimal { unimplemented!() }

fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") }
}

#[mockable]
Expand Down
3 changes: 3 additions & 0 deletions mm2src/coins/utxo/qtum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::*;
use crate::{eth, CanRefundHtlc, CoinBalance, SwapOps, TradePreimageError, TradePreimageValue, ValidateAddressResult};
use common::mm_metrics::MetricsArc;
use common::mm_number::MmNumber;
use ethereum_types::H160;
use futures::{FutureExt, TryFutureExt};

Expand Down Expand Up @@ -491,6 +492,8 @@ impl MarketCoinOps for QtumCoin {
fn display_priv_key(&self) -> String { utxo_common::display_priv_key(&self.utxo_arc) }

fn min_tx_amount(&self) -> BigDecimal { utxo_common::min_tx_amount(self.as_ref()) }

fn min_trading_vol(&self) -> MmNumber { utxo_common::min_trading_vol(self.as_ref()) }
}

impl MmCoin for QtumCoin {
Expand Down
11 changes: 11 additions & 0 deletions mm2src/coins/utxo/utxo_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ pub use chain::Transaction as UtxoTx;
use self::rpc_clients::{electrum_script_hash, UnspentInfo, UtxoRpcClientEnum};
use crate::utxo::rpc_clients::UtxoRpcClientOps;
use crate::{CanRefundHtlc, CoinBalance, FeeApproxStage, TradePreimageError, TradePreimageValue, ValidateAddressResult};
use common::mm_number::MmNumber;
use common::{block_on, Traceable};

const MIN_BTC_TRADING_VOL: &str = "0.00777";

macro_rules! true_or {
($cond: expr, $etype: expr) => {
if !$cond {
Expand Down Expand Up @@ -1316,6 +1319,14 @@ pub fn min_tx_amount(coin: &UtxoCoinFields) -> BigDecimal {
big_decimal_from_sat(coin.dust_amount as i64, coin.decimals)
}

pub fn min_trading_vol(coin: &UtxoCoinFields) -> MmNumber {
if coin.conf.ticker == "BTC" {
return MmNumber::from(MIN_BTC_TRADING_VOL);
}
let dust_multiplier = MmNumber::from(10);
dust_multiplier * min_tx_amount(coin).into()
}

pub fn is_asset_chain(coin: &UtxoCoinFields) -> bool { coin.conf.asset_chain }

pub async fn withdraw<T>(coin: T, req: WithdrawRequest) -> Result<TransactionDetails, String>
Expand Down
3 changes: 3 additions & 0 deletions mm2src/coins/utxo/utxo_standard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::*;
use crate::{CanRefundHtlc, CoinBalance, SwapOps, TradePreimageError, TradePreimageValue, ValidateAddressResult};
use common::mm_metrics::MetricsArc;
use common::mm_number::MmNumber;
use futures::{FutureExt, TryFutureExt};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -386,6 +387,8 @@ impl MarketCoinOps for UtxoStandardCoin {
fn display_priv_key(&self) -> String { utxo_common::display_priv_key(&self.utxo_arc) }

fn min_tx_amount(&self) -> BigDecimal { utxo_common::min_tx_amount(self.as_ref()) }

fn min_trading_vol(&self) -> MmNumber { utxo_common::min_trading_vol(self.as_ref()) }
}

impl MmCoin for UtxoStandardCoin {
Expand Down
16 changes: 8 additions & 8 deletions mm2src/common/mm_number.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::big_int_str::BigIntStr;
use bigdecimal::BigDecimal;
use core::ops::{Add, AddAssign, Div, Mul, Sub};
use num_rational::BigRational;
use num_traits::{Pow, Zero};
use serde::{de, Deserialize, Deserializer, Serialize};
use serde_json::value::RawValue;
use std::str::FromStr;

pub use bigdecimal::BigDecimal;
pub use num_bigint::{BigInt, Sign};
pub use num_rational::BigRational;
pub use paste::paste;

/// Construct a `$name` detailed number that have decimal, fraction and rational representations.
Expand All @@ -32,13 +32,13 @@ macro_rules! construct_detailed {
$crate::mm_number::paste! {
#[derive(Clone, Debug, Serialize)]
pub struct $name {
$base_field: BigDecimal,
[<$base_field _fraction>]: Fraction,
[<$base_field _rat>]: BigRational,
$base_field: $crate::mm_number::BigDecimal,
[<$base_field _fraction>]: $crate::mm_number::Fraction,
[<$base_field _rat>]: $crate::mm_number::BigRational,
}

impl From<MmNumber> for $name {
fn from(mm_num: MmNumber) -> Self {
impl From<$crate::mm_number::MmNumber> for $name {
fn from(mm_num: $crate::mm_number::MmNumber) -> Self {
Self {
$base_field: mm_num.to_decimal(),
[<$base_field _fraction>]: mm_num.to_fraction(),
Expand All @@ -49,7 +49,7 @@ macro_rules! construct_detailed {

#[allow(dead_code)]
impl $name {
pub fn as_ratio(&self) -> &BigRational {
pub fn as_ratio(&self) -> &$crate::mm_number::BigRational {
&self.[<$base_field _rat>]
}
}
Expand Down
54 changes: 36 additions & 18 deletions mm2src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ const MAKER_ORDER_TIMEOUT: u64 = MIN_ORDER_KEEP_ALIVE_INTERVAL * 3;
const TAKER_ORDER_TIMEOUT: u64 = 30;
const ORDER_MATCH_TIMEOUT: u64 = 30;
const ORDERBOOK_REQUESTING_TIMEOUT: u64 = MIN_ORDER_KEEP_ALIVE_INTERVAL * 2;
const MIN_TRADING_VOL: &str = "0.00777";
const MAX_ORDERS_NUMBER_IN_ORDERBOOK_RESPONSE: usize = 1000;

/// Alphabetically ordered orderbook pair
Expand Down Expand Up @@ -958,6 +957,11 @@ enum TakerOrderBuildError {
actual: MmNumber,
threshold: MmNumber,
},
/// Max vol below min base vol
MaxBaseVolBelowMinBaseVol {
max: MmNumber,
min: MmNumber,
},
SenderPubkeyIsZero,
ConfsSettingsNotSet,
}
Expand All @@ -984,6 +988,12 @@ impl fmt::Display for TakerOrderBuildError {
actual.to_decimal(),
threshold.to_decimal()
),
TakerOrderBuildError::MaxBaseVolBelowMinBaseVol { min, max } => write!(
f,
"Max base vol {} is below min base vol: {}",
max.to_decimal(),
min.to_decimal()
),
TakerOrderBuildError::SenderPubkeyIsZero => write!(f, "Sender pubkey can not be zero"),
TakerOrderBuildError::ConfsSettingsNotSet => write!(f, "Confirmation settings must be set"),
}
Expand Down Expand Up @@ -1054,11 +1064,8 @@ impl<'a> TakerOrderBuilder<'a> {

/// Validate fields and build
fn build(self) -> Result<TakerOrder, TakerOrderBuildError> {
let min_vol_threshold = MmNumber::from(MIN_TRADING_VOL);
let min_tx_multiplier = MmNumber::from(10);
let min_base_amount =
(&self.base_coin.min_tx_amount().into() * &min_tx_multiplier).max(min_vol_threshold.clone());
let min_rel_amount = (&self.rel_coin.min_tx_amount().into() * &min_tx_multiplier).max(min_vol_threshold);
let min_base_amount = self.base_coin.min_trading_vol();
let min_rel_amount = self.rel_coin.min_trading_vol();

if self.base_coin.ticker() == self.rel_coin.ticker() {
return Err(TakerOrderBuildError::BaseEqualRel);
Expand Down Expand Up @@ -1086,12 +1093,23 @@ impl<'a> TakerOrderBuilder<'a> {
return Err(TakerOrderBuildError::ConfsSettingsNotSet);
}

let min_volume = self.min_volume.unwrap_or_else(|| min_base_amount.clone());
let price = &self.rel_amount / &self.base_amount;
let base_min_by_rel = &min_rel_amount / &price;
let base_min_vol_threshold = min_base_amount.max(base_min_by_rel);

let min_volume = self.min_volume.unwrap_or_else(|| base_min_vol_threshold.clone());

if min_volume < min_base_amount {
if min_volume < base_min_vol_threshold {
return Err(TakerOrderBuildError::MinVolumeTooLow {
actual: min_volume,
threshold: min_base_amount,
threshold: base_min_vol_threshold,
});
}

if self.base_amount < min_volume {
return Err(TakerOrderBuildError::MaxBaseVolBelowMinBaseVol {
max: self.base_amount,
min: min_volume,
});
}

Expand Down Expand Up @@ -1363,11 +1381,8 @@ impl<'a> MakerOrderBuilder<'a> {
/// Validate fields and build
fn build(self) -> Result<MakerOrder, MakerOrderBuildError> {
let min_price = MmNumber::from(BigRational::new(1.into(), 100_000_000.into()));
let min_vol_threshold = MmNumber::from(MIN_TRADING_VOL);
let min_tx_multiplier = MmNumber::from(10);
let min_base_amount =
(&self.base_coin.min_tx_amount().into() * &min_tx_multiplier).max(min_vol_threshold.clone());
let min_rel_amount = (&self.rel_coin.min_tx_amount().into() * &min_tx_multiplier).max(min_vol_threshold);
let min_base_amount = self.base_coin.min_trading_vol();
let min_rel_amount = self.rel_coin.min_trading_vol();

if self.base_coin.ticker() == self.rel_coin.ticker() {
return Err(MakerOrderBuildError::BaseEqualRel);
Expand Down Expand Up @@ -1395,11 +1410,14 @@ impl<'a> MakerOrderBuilder<'a> {
});
}

let min_base_vol = self.min_base_vol.unwrap_or_else(|| min_base_amount.clone());
if min_base_vol < min_base_amount {
let base_min_by_rel = &min_rel_amount / &self.price;
let base_min_vol_threshold = min_base_amount.max(base_min_by_rel);

let min_base_vol = self.min_base_vol.unwrap_or_else(|| base_min_vol_threshold.clone());
if min_base_vol < base_min_vol_threshold {
return Err(MakerOrderBuildError::MinBaseVolTooLow {
actual: min_base_vol,
threshold: min_base_amount,
threshold: base_min_vol_threshold,
});
}

Expand Down Expand Up @@ -1435,7 +1453,7 @@ impl<'a> MakerOrderBuilder<'a> {
rel: self.rel_coin.ticker().to_owned(),
created_at: now_ms(),
max_base_vol: self.max_base_vol,
min_base_vol: self.min_base_vol.unwrap_or(MIN_TRADING_VOL.into()),
min_base_vol: self.min_base_vol.unwrap_or(self.base_coin.min_trading_vol()),
price: self.price,
matches: HashMap::new(),
started_swaps: Vec::new(),
Expand Down
5 changes: 1 addition & 4 deletions mm2src/lp_ordermatch/orderbook_rpc.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use super::{subscribe_to_orderbook_topic, OrdermatchContext, RpcOrderbookEntry};
use bigdecimal::BigDecimal;
use coins::{address_by_coin_conf_and_pubkey_str, coin_conf};
use common::{mm_ctx::MmArc,
mm_number::{Fraction, MmNumber},
now_ms};
use common::{mm_ctx::MmArc, mm_number::MmNumber, now_ms};
use http::Response;
use num_rational::BigRational;
use num_traits::Zero;
Expand Down
2 changes: 1 addition & 1 deletion mm2src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ use common::{bits256, block_on, calc_total_pages,
executor::{spawn, Timer},
log::{error, info},
mm_ctx::{from_ctx, MmArc},
mm_number::{Fraction, MmNumber},
mm_number::MmNumber,
now_ms, read_dir, rpc_response, slurp, var, write, HyRes, TraceSource, Traceable};
use futures::compat::Future01CompatExt;
use futures::future::{abortable, AbortHandle, TryFutureExt};
Expand Down
12 changes: 6 additions & 6 deletions mm2src/mm2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2302,7 +2302,7 @@ fn orderbook_should_display_base_rel_volumes() {
let orderbook: OrderbookResponse = json::from_str(&rc.1).unwrap();
log!("orderbook "[orderbook]);
assert_eq!(orderbook.asks.len(), 1, "RICK/MORTY orderbook must have exactly 1 ask");
let min_volume = BigRational::new(777.into(), 100000.into());
let min_volume = BigRational::new(1.into(), 10000.into());
assert_eq!(volume, orderbook.asks[0].base_max_volume_rat);
assert_eq!(min_volume, orderbook.asks[0].base_min_volume_rat);

Expand All @@ -2322,7 +2322,7 @@ fn orderbook_should_display_base_rel_volumes() {
let orderbook: OrderbookResponse = json::from_str(&rc.1).unwrap();
log!("orderbook "[orderbook]);
assert_eq!(orderbook.bids.len(), 1, "MORTY/RICK orderbook must have exactly 1 bid");
let min_volume = BigRational::new(777.into(), 100000.into());
let min_volume = BigRational::new(1.into(), 10000.into());
assert_eq!(volume, orderbook.bids[0].rel_max_volume_rat);
assert_eq!(min_volume, orderbook.bids[0].rel_min_volume_rat);

Expand Down Expand Up @@ -2495,7 +2495,7 @@ fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel
"base": base,
"rel": rel,
"price": "1",
"volume": "0.00776",
"volume": "0.00000099",
"cancel_previous": false,
})))
.unwrap();
Expand All @@ -2506,7 +2506,7 @@ fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel
"method": "setprice",
"base": base,
"rel": rel,
"price": "0.00776",
"price": "0.00000099",
"volume": "1",
"cancel_previous": false,
})))
Expand All @@ -2519,7 +2519,7 @@ fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel
"base": base,
"rel": rel,
"price": "1",
"volume": "0.00776",
"volume": "0.00000099",
})))
.unwrap();
assert!(!rc.0.is_success(), "sell success, but should be error {}", rc.1);
Expand All @@ -2530,7 +2530,7 @@ fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel
"base": base,
"rel": rel,
"price": "1",
"volume": "0.00776",
"volume": "0.00000099",
})))
.unwrap();
assert!(!rc.0.is_success(), "buy success, but should be error {}", rc.1);
Expand Down
3 changes: 2 additions & 1 deletion mm2src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes {
"kmd_rewards_info" => hyres(kmd_rewards_info(ctx)),
// "inventory" => inventory (ctx, req),
"list_banned_pubkeys" => hyres(list_banned_pubkeys_rpc(ctx)),
"metrics" => metrics(ctx),
"max_taker_vol" => hyres(max_taker_vol(ctx, req)),
"metrics" => metrics(ctx),
"min_trading_vol" => hyres(min_trading_vol(ctx, req)),
"my_balance" => hyres(my_balance(ctx, req)),
"my_orders" => hyres(my_orders(ctx)),
"my_recent_swaps" => my_recent_swaps(ctx, req),
Expand Down
Loading

0 comments on commit dd24de2

Please sign in to comment.