1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
//! A unified API for abstracting over various exchanges.

pub mod binance;
pub mod gdax;
pub mod hitbtc;
pub mod errors;
pub mod timestamp;
pub mod symbol;
pub mod order_book;
mod query_string;
mod wss;

use futures::prelude::*;
use std::collections::HashMap;
use serde_derive::{Serialize, Deserialize};
use bitflags::bitflags;
use crate::Side;
use crate::tick::{TickUnit, Tickable};
use crate::order_book::LimitUpdate;

use self::timestamp::Timestamped;
use self::symbol::{Symbol, WithSymbol};

pub use self::gdax as coinbase_pro; // Just rename GDAX to its new name.

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// Params needed for an API client.
pub struct Params {
    /// Streaming API address (usually over WebSocket).
    pub streaming_endpoint: String,

    /// REST API endpoint (usually over HTTP).
    pub rest_endpoint: String,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// See https://www.investopedia.com/terms/t/timeinforce.asp.
pub enum TimeInForce {
    /// The order stays on the exchange until it is executed on canceled.
    GoodTilCanceled,

    /// The order must partially fill immediately, the remaining unfilled quantity
    /// is canceled.
    ImmediateOrCancel,

    /// If the order cannot be filled immediately in its entierety, it is rejected.
    FillOrKilll,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// Order type.
pub enum OrderType {
    /// A normal limit order.
    Limit,

    /// A limit order which cannot take liquidity, i.e. an error would be returned by
    /// the exchange if the order crosses the other side of the book.
    LimitMaker,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// An order to be sent through the API.
pub struct Order {
    price: Tickable,
    size: Tickable,
    side: Side,
    #[serde(rename = "type")]
    type_: OrderType,
    time_in_force: TimeInForce,
    time_window: u64,
    order_id: Option<String>,
}

impl Order {
    /// Return a new `Order`, with:
    /// * `price` being the order price
    /// * `size` being the order size
    /// * `side` being `Side::Bid` (buy) or `Side::Ask` (sell)
    pub fn new<T, U>(price: T, size: U, side: Side) -> Self
        where T: Into<Tickable>, U: Into<Tickable>
    {
        Order {
            price: price.into(),
            size: size.into(),
            side,
            type_: OrderType::Limit,
            time_in_force: TimeInForce::GoodTilCanceled,
            time_window: 5000,
            order_id: None,
        }
    }

    /// Set the order type.
    pub fn with_order_type(mut self, order_type: OrderType) -> Self {
        self.type_ = order_type;
        self
    }

    /// Time in force, see https://www.investopedia.com/terms/t/timeinforce.asp.
    pub fn with_time_in_force(mut self, time_in_force: TimeInForce) -> Self {
        self.time_in_force = time_in_force;
        self
    }

    /// Delay until the order becomes invalid if not treated by the server, in ms.
    ///
    /// # Note
    /// Usable only on binance. On Coinbase Pro, the exchange forces the time window
    /// to be 30s. I don't know about HitBTC.
    pub fn with_time_window(mut self, time_window: u64) -> Self {
        self.time_window = time_window;
        self
    }

    /// Generate an id for identifying this order. When possible, the order id will
    /// be equal to `hint`, otherwise it is assured that all ids generated by a call to
    /// this method are distinct.
    pub fn with_order_id<C: ApiClient>(mut self, hint: &str) -> Self {
        self.order_id = Some(C::new_order_id(hint));
        self
    }

    /// Return the order id if one was provided.
    pub fn order_id(&self) -> Option<&str> {
        self.order_id.as_ref().map(|s| s.as_ref())
    }

    /// Return the order price.
    pub fn price(&self) -> &Tickable {
        &self.price
    }

    /// Return the order size.
    pub fn size(&self) -> &Tickable {
        &self.size
    }

    /// Return the order type.
    pub fn order_type(&self) -> OrderType {
        self.type_
    }

    /// Return the chosen time in force.
    pub fn time_in_force(&self) -> TimeInForce {
        self.time_in_force
    }

    /// Return the chosen validity time window.
    pub fn time_window(&self) -> u64 {
        self.time_window
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// An order to cancel a previous order.
pub struct Cancel {
    order_id: String,
    time_window: u64,
}

impl Cancel {
    /// Return a new `Cancel`, with `order_id` identifying the order to cancel.
    pub fn new(order_id: String) -> Self {
        Cancel {
            order_id,
            time_window: 5000,
        }
    }

    /// Delay until the cancel order becomes invalid if not treated by the server, in ms.
    ///
    /// # Note
    /// Usable only on binance. On Coinbase Pro, the exchange forces the time window
    /// to be 30s. I don't know about HitBTC.
    pub fn with_time_window(mut self, time_window: u64) -> Self {
        self.time_window = time_window;
        self
    }

    /// Return the order id to cancel.
    pub fn order_id(&self) -> &str {
        &self.order_id
    }

    /// Return the chosen validity time window.
    pub fn time_window(&self) -> u64 {
        self.time_window
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// An acknowledgment that an order has been treated by the server.
pub struct OrderAck {
    /// ID identifiying the order.
    pub order_id: String,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// An acknowledgment that a cancel order has been treated by the server.
pub struct CancelAck;

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// A notification that some order has been updated, i.e. a trade crossed through this order.
pub struct OrderUpdate {
    /// ID identifying the order being updated.
    pub order_id: String,

    /// Size just consumed by last trade.
    pub consumed_size: TickUnit,

    /// Total remaining size for this order (can be maintained in a standalone way
    /// using the size of the order at insertion time, `consumed_size` and `commission`).
    pub remaining_size: TickUnit,

    /// Price at which the last trade happened.
    pub consumed_price: TickUnit,

    /// Commission amount (warning: for binance this may not be in the same currency as
    /// the traded asset).
    pub commission: TickUnit,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// A liquidity consuming order.
pub struct Trade {
    /// Price in ticks.
    pub price: TickUnit,

    /// Size consumed by the trade.
    pub size: TickUnit,

    /// Side of the maker:
    /// * if `Ask`, then the maker was providing liquidity on the ask side,
    ///   i.e. the consumer bought to the maker
    /// * if `Bid`, then the maker was providing liquidity on the bid side,
    ///   i.e. the consumer sold to the maker
    pub maker_side: Side,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// A notification that some order has expired or was canceled.
pub struct OrderExpiration {
    /// Expired order.
    pub order_id: String,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
/// A notification that some order has been received by the exchange.
pub struct OrderConfirmation {
    /// Unique order id.
    pub order_id: String,

    /// Price at which the order was inserted.
    pub price: TickUnit,

    /// Size at which the order was inserted.
    pub size: TickUnit,

    /// Side of the order.
    pub side: Side,
}

#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
/// A notification that some event happened.
pub enum Notification {
    /// A trade was executed.
    Trade(Timestamped<Trade>),

    /// The limit order book has changed and should be updated.
    LimitUpdates(Vec<Timestamped<LimitUpdate>>),

    /// An order has been inserted.
    OrderConfirmation(Timestamped<OrderConfirmation>),

    /// An order has been updated.
    OrderUpdate(Timestamped<OrderUpdate>),

    /// An order has expired or was canceled.
    OrderExpiration(Timestamped<OrderExpiration>),
}

bitflags! {
    /// Bit flags indicating which type of notification to forward.
    pub struct NotificationFlags: u8 {
        /// Forward limit updates of the order book.
        const ORDER_BOOK = 0b0001;

        /// Forward trades.
        const TRADES = 0b0010;

        /// Forward order confirmations and updates.
        const ORDERS = 0b0100;

        /// Forward all notifications.
        const ALL = Self::ORDER_BOOK.bits | Self::TRADES.bits | Self::ORDERS.bits;
    }
}

/// Generate order ids.
pub trait GenerateOrderId {
    /// Use `hint` for generating an order id. Except for
    /// GDAX, this should just be `== to_owned`.
    fn new_order_id(hint: &str) -> String;
}

#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize)]
/// Account balance for one asset.
pub struct Balance {
    /// Available amount, unticked.
    pub free: String,

    /// Locked amount, unticked.
    pub locked: String,
}

/// A wrapper over a (currency name) => (balance) `HashMap`.
pub type Balances = HashMap<String, Balance>;

/// A trait implemented by clients of various exchanges API.
pub trait ApiClient: GenerateOrderId {
    /// Type returned by the `stream` implementor, used for continuously receiving
    /// notifications.
    type Stream: Stream<Item = Notification, Error = ()> + Send + 'static;

    /// Find a symbol by name.
    fn find_symbol(&self, symbol: &str) -> Option<Symbol>;

    /// Start streaming notifications, only forward those indicated by `flags`.
    fn stream_with_flags(&self, symbol: Symbol, flags: NotificationFlags) -> Self::Stream;

    /// Start streaming notifications.
    fn stream(&self, symbol: Symbol) -> Self::Stream {
        self.stream_with_flags(symbol, NotificationFlags::ALL)
    }

    /// Send an order to the exchange.
    fn order(&self, order: WithSymbol<&Order>)
        -> Box<dyn Future<Item = Timestamped<OrderAck>, Error = errors::OrderError> + Send + 'static>;

    /// Send a cancel order to the exchange.
    ///
    /// # Note
    /// Do no try to cancel an order if said order has not yet been confirmed by the exchange.
    fn cancel(&self, cancel: WithSymbol<&Cancel>)
        -> Box<dyn Future<Item = Timestamped<CancelAck>, Error = errors::CancelError> + Send + 'static>;

    /// Send a ping to the exchange. This can be used to measure the whole roundtrip time,
    /// including authentication and passage through the various software layers. For binance,
    /// the exchange must be pinged regularly in order to keep the listen key alive.
    ///
    /// # Note
    /// Only work for binance right now.
    fn ping(&self)
        -> Box<dyn Future<Item = Timestamped<()>, Error = errors::Error> + Send + 'static>;

    /// Retrieve balances for this account.
    fn balances(&self)
        -> Box<dyn Future<Item = Balances, Error = errors::Error> + Send + 'static>;
}