hugues31 / coinnect

Coinnect is a Rust library aiming to provide a complete access to main crypto currencies exchanges via REST API.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Coinnect handle several Exchanges

Peltoche opened this issue · comments

Ad I understand it the Coinnect client is used for create a generic way to handle client, so the main goal for this feature is to manage several clients at the same time. But for now the Coinnect struct can handle only one Exchange at a time so if we need to make a action on several Exchanges we must create several Coinnect client, which lead to something like this:

fn main() {
    let markets = vec!{Exchange::Bitstamp, Exchange::Kraken, Exchange::Poloniex}

    let clients = Vec::new();

    for market in markets {
        clients.push(Coinnect::new(market, "key", "secret", None).unwrap());
    }

    for client in clients {
        println!("res: {}", client.ticker());
    }
}

This method have some issues:

  • The user must handle his own list of clients
  • We can't improve speed by making all the call asynchonous and parallel
  • The clients doesn't share the same http client

I would propose to modify the Coinnect API to handle several clients, something like this:

fn main() {
    let markets = vec!{Exchange::Bitstamp, Exchange::Kraken, Exchange::Poloniex}

    let clients = Coinnect::new();
    // or simply Coinnect::new_from_file()

    for market in markets {
        // EDIT: little typo fix here.
        clients.add(market, "key", "secret", None).unwrap())
    }

    let mut res;

    res = clients.ticker()
        .from(Exchange::Bitstamps)
        .from(Exchange::Kraken)
        .exec()
        .unwrap();

    res = clients.ticker()
        .from_all()
        .exec()
        .unwrap();
}
  • Only one client (and http client)
  • Requests can be parallel
  • Avoid code for clients
  • Wrap all the clients into a simple struct (no more Box<ExchangeAPI for the user)

In my mind I had the first option because it allows the user to handle everything and coinnect just encapsulates the exchange.

But I like the performance improvements that would arise from connecting to the exchanges in parallel, isolating the threading from the user.

In your first example, the for loop:

for client in clients {
        println!("res: {}", client.ticker());
}

could be made parallel easily using Futures (issue #9)

I don't see this

 res = clients.ticker()
        .from(Exchange::Bitstamps)
        .from(Exchange::Kraken)
        .exec()
        .unwrap();

very convenient to use (at least for my use, I am making a trading bot, pulling public data and making private requests).

But maybe we are missing a util function that return a list of mutable Coinnect default structs. So looping as you write:

let markets = vec!{Exchange::Bitstamp, Exchange::Kraken, Exchange::Poloniex};
for market in markets {
        clients.push(Coinnect::new(market, "key", "secret", None).unwrap());
}

is not as convenient as it could be (for an application that synthesize the global market for example)

@hugues31 the main goal is to avoid the use of Futures for users which could simplify a lot the code for them.

For me there is a limited number of case where the asynchronous is interesting and we can handle them in the lib like the multi-call in parallel (it's probably the only one). So exposing Futures for users is adding useless complexity. But I don't know all the use cases and this is correct only if the user want to work with synchronous code.

So yeah, finally this is a solution only if we choose not to return Futures in APIs.

Did you mean (second example)

clients.add(market, "key", "secret", None).unwrap());

instead of ?

Coinnect::add(market, "key", "secret", None).unwrap())

Anyway, if I understand correctly, the generic_api.rs example becomes (with ticker returning a Future) :

fn main() {
    let mut client = Coinnect::new();
    client.add(Exchange::Kraken, "key", "secret", None).unwrap());

    res = client.ticker(ETC_BTC)
        .from(Exchange::Kraken)
        .exec()
        .unwrap();

    println!("ETC_BTC last trade price is {}.",
             res.wait().unwrap()[0].last_trade_price); // if ticker returns a Vec, since we only have 1 exchange
}

Instead of this deadly simple synchronous call:

fn main() {
    let mut my_api = Coinnect::new(Kraken, "api_key", "api_secret", None).unwrap();
    let ticker = my_api.ticker(ETC_BTC);

    println!("ETC_BTC last trade price is {}.",
             ticker.unwrap().last_trade_price);
}

As @crackcomm said in #9 the core could be asynchronous and all methods can return Future. To make it synchronous, the user just have to call wait(), it's yet simple but remains asynchronous for those who want that.

 res = clients.ticker()
        .from(Exchange::Bitstamps)
        .from(Exchange::Kraken)
        .exec()
        .unwrap();

is finally no more than a wrapper around multiple asynchronous ticker and returns only when all ticker are received, so that ticker() is synchronous for the user but benefits from the asynchronous performance improvements (in case of course of multiple requests).

I think this issue should mature some more time for now