Skip to content

Commit

Permalink
add: choices market type (spot/perpetual)
Browse files Browse the repository at this point in the history
  • Loading branch information
mirumirumi committed May 5, 2023
1 parent 9d150b9 commit 8e18a47
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 27 deletions.
18 changes: 17 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{fmt::Debug, str::FromStr};

use anyhow::{anyhow, ensure, Error};
use chrono::{DateTime, Utc};
use clap::{ArgAction, Parser, Subcommand};
use clap::{ArgAction, Parser, Subcommand, ValueEnum};
use regex::Regex;

use crate::{
Expand Down Expand Up @@ -35,6 +35,10 @@ pub struct Cli {
#[arg(short = 's', long, default_value = "BTC/USDT")]
pub symbol: String,

/// Market type (this perpetual refers to the "never-expiring derivative" commonly used by most exchanges)
#[arg(short = 't', long = "type", default_value = "spot")]
pub type_: MarketType,

/// Specify if you want the latest data for the past range (cannot be used with `--term-start`, `--term-end`)
#[arg(long, action = ArgAction::SetTrue)]
pub past: Option<bool>,
Expand Down Expand Up @@ -181,10 +185,17 @@ pub enum Commands {
Guide {},
}

#[derive(Debug, Clone, ValueEnum)]
pub enum MarketType {
Spot,
Perpetual,
}

#[derive(Debug, Clone)]
pub struct ParsedArgs {
pub exchange: Exchange,
pub symbol: String,
pub type_: MarketType,
pub past: bool,
pub range: Option<DurationAndUnit>,
pub term_start: Option<i64>,
Expand All @@ -200,6 +211,7 @@ impl ParsedArgs {
let parsed_args = ParsedArgs {
exchange,
symbol: value.symbol,
type_: value.type_,
past: value.past.unwrap_or(false),
range: match value.range {
Some(range) => Some(range.parse::<DurationAndUnit>()?),
Expand Down Expand Up @@ -332,6 +344,7 @@ mod tests {
let args = ParsedArgs {
exchange: Exchange::Binance(Binance::new()),
symbol: String::new(),
type_: MarketType::Spot,
past: false,
range: None,
term_start,
Expand All @@ -350,6 +363,7 @@ mod tests {
let args = ParsedArgs {
exchange: Exchange::Binance(Binance::new()),
symbol: String::new(),
type_: MarketType::Spot,
past: true,
range: Some(DurationAndUnit(1, TermUnit::Day)),
term_start: None,
Expand Down Expand Up @@ -378,6 +392,7 @@ mod tests {
let args = ParsedArgs {
exchange: Exchange::Binance(Binance::new()),
symbol: String::new(),
type_: MarketType::Spot,
past: false,
range: None,
term_start: Some(946684800000),
Expand All @@ -402,6 +417,7 @@ mod tests {
let args = ParsedArgs {
exchange: Exchange::Binance(Binance::new()),
symbol: String::new(),
type_: MarketType::Spot,
past: false,
range: None,
term_start: Some(946684800000),
Expand Down
8 changes: 5 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ impl ExchangeResponseError {
}

pub fn too_many_requests() -> Error {
anyhow!(
"Too many requests were rejected by the exchange's server. Let's have some coffee ☕."
)
anyhow!("Request denied due to exceeding rate limit. Let's have some coffee ☕.")
}

pub fn no_support_type() -> Error {
anyhow!("This exchange does not support the market type.")
}

pub fn wrap_error(err: String) -> Error {
Expand Down
2 changes: 1 addition & 1 deletion src/exchange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub trait Retrieve: Debug {

fn fit_symbol_to_req(&self, symbol: &str) -> Result<String, Error>;

// Some exchange intervals may be invalid (reason using `Result`)
// Some exchange intervals may be invalid (why using `Result`)
fn fit_interval_to_req(&self, interval: &DurationAndUnit) -> Result<String, Error>;

fn parse_as_kline(&self, data: String) -> Vec<Kline>;
Expand Down
35 changes: 26 additions & 9 deletions src/exchange/binance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use crate::{args::*, error::*, exchange::*, unit::*};

#[derive(Debug, Clone)]
pub struct Binance {
endpoint: String,
limit: i32,
endpoint_spot: String,
endpoint_perpetual: String,
limit_spot: i32,
limit_perpetual: i32,
}

#[derive(Deserialize)]
Expand All @@ -21,8 +23,10 @@ struct ResponseOnError {
impl Binance {
pub fn new() -> Self {
Binance {
endpoint: "https://data.binance.com/api/v3/klines".to_string(),
limit: 1000,
endpoint_spot: "https://data.binance.com/api/v3/klines".to_string(),
endpoint_perpetual: "https://fapi.binance.com/fapi/v1/klines".to_string(),
limit_spot: 1000,
limit_perpetual: 1500,
}
}

Expand All @@ -36,17 +40,17 @@ impl Binance {
let endpoint = match random_number {
0 => {
// This means we can use `https://api.binance.com` as is
self.endpoint.clone()
self.endpoint_spot.clone()
}
num => {
let re = Regex::new(r"https://api\.binance").unwrap();
re.replace(&self.endpoint, format!("https://api{}.binance", num))
re.replace(&self.endpoint_spot, format!("https://api{}.binance", num))
.to_string()
}
};

Binance {
endpoint,
endpoint_spot: endpoint,
..self.clone()
}
}
Expand All @@ -59,10 +63,23 @@ impl Retrieve for Binance {
("interval", self.fit_interval_to_req(&args.interval)?),
("startTime", args.term_start.unwrap().to_string()),
("endTime", args.term_end.unwrap().to_string()),
("limit", self.limit.to_string()),
(
"limit",
match args.type_ {
MarketType::Spot => self.limit_spot.to_string(),
MarketType::Perpetual => self.limit_perpetual.to_string(),
},
),
];

let res = client.get(&self.endpoint).query(params).send()?.text()?;
let res = client
.get(match args.type_ {
MarketType::Spot => &self.endpoint_spot,
MarketType::Perpetual => &self.endpoint_perpetual,
})
.query(params)
.send()?
.text()?;

if let Ok(response) = serde_json::from_str::<ResponseOnError>(&res) {
match response.code {
Expand Down
4 changes: 4 additions & 0 deletions src/exchange/bitbank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ impl Bitbank {

impl Retrieve for Bitbank {
fn fetch(&self, args: &ParsedArgs, client: &Client) -> Result<String, Error> {
if let MarketType::Perpetual = args.type_ {
return Err(ExchangeResponseError::no_support_type());
}

let interval = self.fit_interval_to_req(&args.interval)?;
let endpoint = self.make_url(
self.fit_symbol_to_req(&args.symbol)?,
Expand Down
4 changes: 4 additions & 0 deletions src/exchange/bitmex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ impl Bitmex {

impl Retrieve for Bitmex {
fn fetch(&self, args: &ParsedArgs, client: &Client) -> Result<String, Error> {
if let MarketType::Spot = args.type_ {
return Err(ExchangeResponseError::no_support_type());
}

let params = &[
("binSize", self.fit_interval_to_req(&args.interval)?),
("symbol", self.fit_symbol_to_req(&args.symbol)?),
Expand Down
19 changes: 8 additions & 11 deletions src/exchange/bybit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,9 @@ use crate::{args::*, error::*, exchange::*, unit::*};
#[derive(Debug, Clone)]
pub struct Bybit {
endpoint: String,
// category: Option<Category>,
limit: i32,
}

// #[derive(Debug, Clone)]
// enum Category {
// Spot,
// Linear,
// Inverse, // Not used
// }

#[derive(Deserialize)]
struct Response {
#[serde(alias = "retCode")]
Expand All @@ -29,7 +21,7 @@ struct Response {
}

#[derive(Deserialize)]
// In case of error, to be empty `{}` (reason all fields are optional)
// In case of error, to be empty `{}` (why all fields are optional)
struct ResultInResponse {
#[allow(dead_code)]
category: Option<String>,
Expand All @@ -42,7 +34,6 @@ impl Bybit {
pub fn new() -> Self {
Bybit {
endpoint: "https://api.bybit.com/v5/market/kline".to_string(),
// category: None,
limit: 200,
}
}
Expand All @@ -51,7 +42,13 @@ impl Bybit {
impl Retrieve for Bybit {
fn fetch(&self, args: &ParsedArgs, client: &Client) -> Result<String, Error> {
let params = &[
("category", "spot".to_string()),
(
"category",
match args.type_ {
MarketType::Spot => "spot".to_string(),
MarketType::Perpetual => "linear".to_string(),
},
),
("symbol", self.fit_symbol_to_req(&args.symbol)?),
("interval", self.fit_interval_to_req(&args.interval)?),
("start", args.term_start.unwrap().to_string()),
Expand Down
12 changes: 10 additions & 2 deletions src/exchange/okx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct Response {
impl Okx {
pub fn new() -> Self {
Okx {
endpoint: "https://www.okx.com/api/v5/market/candles".to_string(),
endpoint: "https://www.okx.com/api/v5/market/history-candles".to_string(),
limit: 300,
}
}
Expand All @@ -30,7 +30,15 @@ impl Okx {
impl Retrieve for Okx {
fn fetch(&self, args: &ParsedArgs, client: &Client) -> Result<String, Error> {
let params = &[
("instId", self.fit_symbol_to_req(&args.symbol)?),
(
"instId",
match args.type_ {
MarketType::Spot => self.fit_symbol_to_req(&args.symbol)?,
MarketType::Perpetual => {
format!("{}-SWAP", self.fit_symbol_to_req(&args.symbol)?)
}
},
),
("bar", self.fit_interval_to_req(&args.interval)?),
("before", (args.term_start.unwrap() - 1).to_string()), // Opposite of the word meaning
("after", (args.term_end.unwrap() + 1).to_string()), // Same as above
Expand Down

0 comments on commit 8e18a47

Please sign in to comment.