Gemini API - Practical Assignment

If you have troubles with this assignment, please ask questions in this thread.

My implementation of below API calls in “Gemini API - Practical Assignment”

  1. Use getMyAvailableBalances to see your current balances.
  2. Create a few (3-5) different buy limit orders using newOrder function. Make sure that the price is too low for Gemini to fulfil your buy order.
  3. Using getMyActiveOrders get a list of all your active orders. Do you recognise the orders you created in the last step?
  4. Cancel your orders using cancelAllActiveOrders .
  5. Using getMyActiveOrders get a list of all your active orders. Have they been cancelled?

@ivan , Is it ok to put the solution here?

I can recognize the generated orders and they do get cancelled eventually.

My solution :


const main = require("async-main").default;
const GeminiAPI = require("gemini-api").default;

const JsonBeautify = require("json-beautify");


const secret = "2zLLJdd7tptRGUy1wEqafuaAtztv";
const key = "account-75fGnAH8Fpkrh76UqZYW";


const geminiApiConfig = 
{
   key,
   secret,
   sandbox: true
};
const restClient = new GeminiAPI(geminiApiConfig);


function JsonBeautifyDefault(json)
{
	const result = 
		JsonBeautify(
			/*value:*/ json,
			/*replacer:*/ null,
			/*space:*/ 2,
			/*limit:*/ 80
		);

	return result;
}


const ORDERS_COUNT = 3;
function GenerateOrders()
{
	var result = [];

	for (i = 0; i < ORDERS_COUNT; i++)
	{
		const orderDto = 
		{
			amount: 10 * (i + 1),
			price: 100 * (ORDERS_COUNT - i),
			side: "buy",
			symbol: "BTCUSD"
		};

		result.push(orderDto);
	}

	return result;
}


async function ExecAssignmentLogicAsync()
{
	try
	{
		const orders = GenerateOrders();
		console.log('generated orders : ' + JsonBeautifyDefault(orders));

		for (i = 0; i < ORDERS_COUNT; i++)
		{
			// TODO: maybe run in parallel and apply map/reduce 
			await restClient.newOrder(orders[i]);
		}

		var activeOrders = await restClient.getMyActiveOrders();
		console.log('active orders : ' + JsonBeautifyDefault(activeOrders));

		await restClient.cancelAllActiveOrders();
		console.log('orders cancelled');
		var activeOrdersAfterCancel = await restClient.getMyActiveOrders();

		console.log('after cancel : ' + JsonBeautifyDefault(activeOrdersAfterCancel));
	}
	catch (error)
	{
		console.log(error);
	}
}

main( ExecAssignmentLogicAsync );

Output :


1 Like

When submitting 4 back-to-back orders to Gemini, always one of them would result in an error with the following response:

{ result: ‘error’,
reason: ‘InvalidNonce’,
message:
‘Nonce ‘1564632155050’ has not increased since your last call to the Gemini API.’ }

My guess was that it always receives two of my orders during the same millisecond, so I wrote a sleep(milliseconds) function and added sleep(1) after each order. This solved the issue, but I’m not sure if this is the most elegant way to do this. If anyone or @Ivan have a suggestion or know the actual reason why I was getting that error, please let me know. Thanks!

3 Likes

I never had this issue before, but it seems like you need to recreate your API credentials, please check here: https://github.com/butor/blackbird/issues/38

Thanks for the link, Ivan! This didn’t solve my issue though, I’ll see what else I can do, maybe I’ll just keep my sleep(1 millisecond) function for now. Anyways, this is a fun course, so thank you for the material and support!

unfortunately I wasn’t able to install YARN in Terminal… some permission issues which I cannot solve without sb helping me in person.

Therefore I can only guess at this point but I can’t wait to use my own trading bot

  1. see my balances on every asset

  2. –

  3. see my open orders

  4. ok

  5. should have been cancelled if 4) was executed correctly.

Hi. I know it has past much time since you wrote but i started this course some days ago so i came now to this section. If you haven’t find help look at this if it is your problem with the yarn terminal with permision of signing. I had some issues with this

I have tried to combine every step with each other and for every step i have made, i made a printscreen

  1. Use getMyAvailableBalances to see your current balances.

  1. Create a few (3-5) different buy limit orders using newOrder function. Make sure that the price is too low for Gemini to fulfil your buy order.

  1. Using getMyActiveOrders get a list of all your active orders. Do you recognise the orders you created in the last step?

  2. Cancel your orders using cancelAllActiveOrders.

  1. Using getMyActiveOrders get a list of all your active orders. Have they been cancelled?

I know theoretically how to do it. But I can’t in practice. I am working on a Mac and I couldn’t figure out how to make it work on my computer because you are only giving advice for people with Windows.

@filip
@ivan
API-Overview

Gemini offers both public and private REST APIs.

Public REST APIs provide market data such as:

  • current order book
  • recent trading activity
  • trade history

Private REST APIs allow you to manage both orders and funds:

  • place and cancel orders
  • see your active orders
  • see your trading history and trade volume
  • get your available balances

In order to access the public API, you don’t even need an API key, because this information is for everyone. When it comes to your account and your funds, you will require an API key and an API secret to use the private REST API. Please note that the API secret will only be shown to you when you create your API key. You have to save the secret on a secure place. If you lose your API secret you simply should delete the corresponding API key and create a new one.

For accessing the Gemini sandbox API with JavaScript you will have to define a few things:

  1. The Node module you use, in this case gemini-api
  2. Your API key
  3. Your API secret
  4. Your Rest Client
const GeminiAPI = require("gemini-api").default;
const key="account-vR2hcSQbs2EIYA1Kvw3e";
const secret="3zuoXDdFUvbLyHoQRB1Ds5Cqm7Y6";
const restClient = new GeminiAPI({key, secret, sandbox:true});

Please note that the key and secret provided above no longer work since I have deleted this API key.

With this preparation you can use the sandbox API commands of the Gemini exchange using the wrapper “gemini-api”:

GetAllSymbols()

This API call will return all symbols or trading pairs available at the Gemini exchange in an array.

Call:

restClient.getAllSymbols()
.then(response => console.log(response))
.catch(error => console.log(error));

You could write the code above in a line as well.

Reply

[
  'btcusd', 'ethbtc',
  'ethusd', 'bchusd',
  'bchbtc', 'bcheth',
  'ltcusd', 'ltcbtc',
  'ltceth', 'ltcbch',
  'zecusd', 'zecbtc',
  'zeceth', 'zecbch',
  'zecltc'
]

getTicker(symbol)

For this API call you will need a valid parameter, for example the BTC/USD trading pair “btcusd”. You will get information about the current bid, the current ask the volume denominated in both currencies of the trading pair, a timestamp and the last price traded.

Call:

restClient.getTicker("btcusd")
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  bid: '7192.25',
  ask: '7192.84',
  volume: {
    BTC: '63.4010402',
    USD: '455658.977693172',
    timestamp: 1587333600000
  },
  last: '7192.25'
}

getOrderBook(symbol,params={})

This will return the current order book of the market defined in symbol as two arrays (bids / asks).

Since this is a public API command you can also see the result of this request in your browser.
Try https://api.gemini.com/v1/book/btcusd or https://api.sandbox.gemini.com/v1/book/btcusd to view it in your browser and you will see that it works!

Call:

restClient.getOrderBook("btcusd")
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  bids: [
    { price: '7171.64', amount: '3.2', timestamp: '1587334797' },

                            ...

    { price: '79.97', amount: '7', timestamp: '1587334797' }
  ],
  asks: [
    { price: '7174.64', amount: '0.129688', timestamp: '1587334797' },

                            ...

    { price: '150000.00', amount: '1', timestamp: '1587334797' }
  ]
}

Please note that I omitted most of the orders in between ( … ).

getTradeHistory(symbol,params={})

This will return the trades that have executed since the specified timestamp. Timestamps are either seconds or milliseconds since the epoch (1970-01-01).

Access via HTTP request in your browser:
https://api.gemini.com/v1/trades/btcusd or https://api.sandbox.gemini.com/v1/trades/btcusd

Call:

restClient.getTradeHistory("btcusd")
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

[
  {
    timestamp: 1587335536,
    timestampms: 1587335536731,
    tid: 570522542,
    price: '7195.78',
    amount: '0.002',
    exchange: 'gemini',
    type: 'buy'
  },
  
                            ...
							
  {
    timestamp: 1587335070,
    timestampms: 1587335070613,
    tid: 570521335,
    price: '7191.00',
    amount: '0.002',
    exchange: 'gemini',
    type: 'buy'
  }
]

getCurrentAuction(symbol)

For more details please refer to https://docs.gemini.com/rest-api/#current-auction.

HTTP REQUEST (example for symbol “btcusd”)

GET https://api.gemini.com/v1/auction/btcusd

SANDBOX (example for symbol “btcusd”)

Base URL can be changed to https://api.sandbox.gemini.com/v1/auction/btcusd for test purposes.

Call:

restClient.getCurrentAuction("btcusd")
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:
{ next_auction_ms: 1574542800000 }

Please note that the reply from Gemini exchange is different:
{"closed_until_ms":1587351600000,"next_auction_ms":1587380400000}

getAuctionHistory(symbol, params={})

HTTP REQUEST (example for symbol “btcusd”)

https://api.sandbox.gemini.com/v1/auction/btcusd/history

SANDBOX (example for symbol “btcusd”)

Base URL can be changed to https://api.sandbox.gemini.com/v1/auction/btcusd/history for test purposes.

Call:

restClient.getAuctionHistory("btcusd")
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

[
  {
    timestamp: 1574370000,
    timestampms: 1574370000000,
    eid: 325908450,
    auction_id: 3740,
    event_type: 'auction',
    auction_result: 'failure',
    highest_bid_price: '7576.00',
    lowest_ask_price: '7582.14',
    unmatched_collar_quantity: '0',
    collar_price: '7579.07'
  },
  
                             ...
							 
  {
    timestamp: 1574110560,
    timestampms: 1574110560000,
    eid: 322543297,
    auction_id: 3725,
    event_type: 'indicative',
    auction_result: 'failure',
    highest_bid_price: '8180.37',
    lowest_ask_price: '8180.38',
    unmatched_collar_quantity: '0',
    collar_price: '8180.375'
  }
]

newOrder(params={})

For more details please refer to https://docs.gemini.com/rest-api/#new-order.

For this API call a few parameters have to be provided:

  • symbol : The trading pair, for example “btcusd”
  • amount : Amount of BTC
  • price : Price in USD / BTC.
  • side : “buy” or “sell”

More complex orders may require additional paramaters, for example “stop_price” for stop limit orders.

In the following example we will set a limit order to buy 10 BTC for a price of 100 USD for each BTC.

Call:

restClient.newOrder({amount:10,price:100,side:"buy",symbol:"btcusd"})
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  order_id: '570535948',
  id: '570535948',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587340365',
  timestampms: 1587340365352,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: '_MDxhjxig',
  options: [],
  price: '100.00',
  original_amount: '10',
  remaining_amount: '10'
}

cancelOrder({order_id})

This API call is used for cancelling an existing order. In order to specify which order is to be cancelled the unique order id has to be provided.

The API key you use to access this endpoint must have the Trader role assigned. API keys without the Trader role assigned lack the authorization to execute this API call.

In this example we will cancel the order created in the newOrder API call:

restClient.cancelOrder({order_id:570535948})
.then(response => console.log(response))
.catch(error => console.log(error));

Upon a successful call, the response will be as follows:

{
  order_id: '570535948',
  id: '570535948',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587340365',
  timestampms: 1587340365352,
  is_live: false,
  is_cancelled: true,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: '_MDxhjxig',
  reason: 'Requested',
  options: [],
  price: '100.00',
  original_amount: '10',
  remaining_amount: '10'
}

Note the is_cancelled node will have a value of ‘true’.

cancelAllSessionOrders()

This will cancel all orders opened by this session.


Call:

restClient.cancelAllSessionOrders()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  result: 'ok',
  details: { cancelledOrders: [ 570506375, 570506767 ], cancelRejects: [] }
}

cancelAllActiveOrders()

This will cancel all outstanding orders created by all sessions owned by this account, including interactive orders placed through the UI.

Note that this cancels orders that were not placed using this API key.

Typically Cancel All Session Orders is preferable, so that only orders related to the current connected session are cancelled.


Call:

restClient.cancelAllActiveOrders()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  result: 'ok',
  details: {
    cancelledOrders: [ 570542063, 570542068, 570542071 ],
    cancelRejects: []
  }
}

getMyOrderStatus({order_id})

This API call will return all status information of the order provided in order_id.

Call:

restClient.getMyOrderStatus({order_id:'570643084'})
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  order_id: '570643085',
  id: '570643085',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587378577',
  timestampms: 1587378577073,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: '5OKP-y8tJ',
  options: [],
  price: '100.00',
  original_amount: '10',
  remaining_amount: '10'
}

Providing a wrong order id you will try to access information of a non-existing order and this will result in the following reply:

{
  result: 'error',
  reason: 'OrderNotFound',
  message: 'Order 570643084 not found'
}

getMyPastTrades(params={})

The field symbol must be provided as a parameter.

Calling the API without providing the field symbol:

restClient.getMyPastTrades({})
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  result: 'error',
  reason: 'MissingSymbol',
  message: "Order was missing required field 'symbol' with supported values ['BTCUSD', 'ETHBTC', 'ETHUSD', 'BCHUSD', 'BCHBTC', 'BCHETH', 'LTCUSD', 'LTCBTC', 'LTCETH', 'LTCBCH', 'ZECUSD', 'ZECBTC', 'ZECETH', 'ZECBCH', 'ZECLTC']"
}

Call (without any trades):

restClient.getMyPastTrades({symbol:"btcusd"})
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

[]

If there had been trades in the past the reply will look like this:

[ {
    "price": "3648.09",
    "amount": "0.0027343246",
    "timestamp": 1547232911,
    "timestampms": 1547232911021,
    "type": "Buy",
    "aggressor": True,
    "fee_currency": "USD",
    "fee_amount": "0.024937655575035",
    "tid": 107317526,
    "order_id": "107317524",
    "exchange": "gemini",
    "is_auction_fill": False
  }, {
    "price": "3633.00",
    "amount": "0.00423677",
    "timestamp": 1547220640,
    "timestampms": 1547220640195,
    "type": "Buy",
    "aggressor": False,
    "fee_currency": "USD",
    "fee_amount": "0.038480463525",
    "tid": 106921823,
    "order_id": "106817811",
    "exchange": "gemini",
    "is_auction_fill": False
} ]

getMyTradeVolume()

This API call lists the trading volume for the different markets traded by a trader.

For further information please refer to https://docs.gemini.com/rest-api/#get-trade-volume.

Call:

restClient.getMyTradeVolume()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply for account without any trading volume:

[ [] ]

Reply for account with some trading volume:

[
    [
        {
            "symbol": "btcusd",
            "base_currency": "BTC",
            "notional_currency": "USD",
            "data_date": "2019-01-10",
            "total_volume_base": 8.06021756,
            "maker_buy_sell_ratio": 1,
            "buy_maker_base": 6.06021756,
            "buy_maker_notional": 23461.3515203844,
            "buy_maker_count": 34,
            "sell_maker_base": 0,
            "sell_maker_notional": 0,
            "sell_maker_count": 0,
            "buy_taker_base": 0,
            "buy_taker_notional": 0,
            "buy_taker_count": 0,
            "sell_taker_base": 2,
            "sell_taker_notional": 7935.66,
            "sell_taker_count": 2
        },
        {
            "symbol": "ltcusd",
            "base_currency": "LTC",
            "notional_currency": "USD",
            "data_date": "2019-01-11",
            "total_volume_base": 3,
            "maker_buy_sell_ratio": 0,
            "buy_maker_base": 0,
            "buy_maker_notional": 0,
            "buy_maker_count": 0,
            "sell_maker_base": 0,
            "sell_maker_notional": 0,
            "sell_maker_count": 0,
            "buy_taker_base": 3,
            "buy_taker_notional": 98.22,
            "buy_taker_count": 3,
            "sell_taker_base": 0,
            "sell_taker_notional": 0,
            "sell_taker_count": 0
        }
    ]
]

In the following steps the tasks of the assignment will be shown:

  1. Use getMyAvailableBalances to see your current balances.

This API call will return the current balances of an account.

Call:

restClient.getMyAvailableBalances()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

[
  {
    type: 'exchange',
    currency: 'ZEC',
    amount: '20000',
    available: '20000',
    availableForWithdrawal: '20000'
  },
  {
    type: 'exchange',
    currency: 'ETH',
    amount: '20000',
    available: '20000',
    availableForWithdrawal: '20000'
  },
  {
    type: 'exchange',
    currency: 'BCH',
    amount: '20000',
    available: '20000',
    availableForWithdrawal: '20000'
  },
  {
    type: 'exchange',
    currency: 'LTC',
    amount: '20000',
    available: '20000',
    availableForWithdrawal: '20000'
  },
  {
    type: 'exchange',
    currency: 'BTC',
    amount: '1000',
    available: '1000',
    availableForWithdrawal: '1000'
  },
  {
    type: 'exchange',
    currency: 'USD',
    amount: '100000.00',
    available: '98999.00',
    availableForWithdrawal: '98999.00'
  }
]
  1. Create a few (3-5) different buy limit orders using newOrder function. Make sure that the price is too low for Gemini to fulfil your buy order.

This code will create 5 different buy limit orders using the newOrder function. In order to keep the code small I used a for loop to get different prices for my buy orders:

for (var buyPrice = 100; buyPrice <= 500; buyPrice += 100){
    restClient.newOrder({amount:10,price:buyPrice.toString(),side:"buy",symbol:"btcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error));
}

The first two API calls worked well but for the last three calls the following error occured in the reply:

{
  result: 'error',
  reason: 'InvalidNonce',
  message: "Nonce '1587385329129' has not increased since your last call to the Gemini API."
}
{
  result: 'error',
  reason: 'InvalidNonce',
  message: "Nonce '1587385329129' has not increased since your last call to the Gemini API."
}
{
  result: 'error',
  reason: 'InvalidNonce',
  message: "Nonce '1587385329129' has not increased since your last call to the Gemini API."
}

The Nonce parameter is handled by the wrapper and we don’t have to take care of it. However this parameter is the number of milliseconds passed since the 1th of January 1970. As you can see in the reply above the Nonce parameter is exactly the same for all three API calls which means that these API calls were performed in the same millisecond. This resulted in an error.

In order to prevent this problem I used an empty while loop. The condition of this while loop will turn from true to false after 1000 milliseconds have passed, since Date.now() is the number of milliseconds passed since the 1st January of 1970.

Call:

for (var buyPrice = 100; buyPrice <= 500; buyPrice += 100){
    restClient.newOrder({amount:10, price:buyPrice, side:"buy", symbol:"btcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error));
    //Wait for 1000 milliseconds
    var enteredLoop = Date.now();
    while (Date.now() - enteredLoop < 1000) {};
}

Reply:

{
  order_id: '570682079',
  id: '570682079',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587392420',
  timestampms: 1587392420410,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'seXsTybNo',
  options: [],
  price: '100.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570682082',
  id: '570682082',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587392420',
  timestampms: 1587392420513,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'CJ16UcoPj',
  options: [],
  price: '300.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570682085',
  id: '570682085',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587392420',
  timestampms: 1587392420610,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'KK4F_UJ2S',
  options: [],
  price: '400.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570682088',
  id: '570682088',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587392420',
  timestampms: 1587392420710,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'jQsioezNd',
  options: [],
  price: '200.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570682094',
  id: '570682094',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587392420',
  timestampms: 1587392420811,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'a5MYKBe91',
  options: [],
  price: '500.00',
  original_amount: '10',
  remaining_amount: '10'
}

But looking closely at the reply one can see that the time between the API calls is roughly only about 100 milliseconds. Furthermore the sequence of the orders seems strange:

  1. 100USD
  2. 300USD
  3. 400USD
  4. 200USD
  5. 500USD


If you truly want to halt the execution of Javascript things are getting more complicated. First you will need a Sleep function. The Sleep function will return a promise object whose status will be set to resolved after the given time in milliseconds:

function Sleep(milliseconds) {
   return new Promise(resolve => setTimeout(resolve, milliseconds));
}

To use this functionality you will have to call your function using the keyword async. Without the async keyword you are not allowed to use the await statement:

async function callAPI() {
  for (var buyPrice = 100; buyPrice <= 500; buyPrice += 100){
    restClient.newOrder({amount:10, price:buyPrice, side:"buy", symbol:"btcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error));
    //Wait for 1000 milliseconds
    await Sleep(1000);
  }
}

callAPI();

The asynchronous function callAPI calls the function Sleep. With the await statement the execution is halted for the given time, which is 1000 milliseconds in our example. Without await statement the execution of the function will simply go on because it would not wait for the return of the promise object to get the status resolved.

Reply:

{
  order_id: '570693582',
  id: '570693582',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587396549',
  timestampms: 1587396549267,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'lMX_fkJcL',
  options: [],
  price: '100.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570693690',
  id: '570693690',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587396550',
  timestampms: 1587396550141,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'qs3UEZgBP',
  options: [],
  price: '200.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570693696',
  id: '570693696',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587396551',
  timestampms: 1587396551142,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'nZ8ViwbQa',
  options: [],
  price: '300.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570693699',
  id: '570693699',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587396552',
  timestampms: 1587396552142,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'BUZklEFFu',
  options: [],
  price: '400.00',
  original_amount: '10',
  remaining_amount: '10'
}
{
  order_id: '570693702',
  id: '570693702',
  symbol: 'btcusd',
  exchange: 'gemini',
  avg_execution_price: '0.00',
  side: 'buy',
  type: 'exchange limit',
  timestamp: '1587396553',
  timestampms: 1587396553149,
  is_live: true,
  is_cancelled: false,
  is_hidden: false,
  was_forced: false,
  executed_amount: '0',
  client_order_id: 'SeOACkrjQ',
  options: [],
  price: '500.00',
  original_amount: '10',
  remaining_amount: '10'
}

During the execution of the code every API reply appeared with a true delay of at least 1000 milliseconds. This can be confirmed by looking at the field timestamp. And this time the sequence of the orders is correct:

  1. 100USD
  2. 200USD
  3. 300USD
  4. 400USD
  5. 500USD

  1. Using getMyActiveOrders get a list of all your active orders. Do you recognise the orders you created in the last step?

getMyActiveOrders()

This API call will return all active orders of an account. The result is array of the results of getMyOrderStatus({order_id}) of all your live orders.

Call:

restClient.getMyActiveOrders()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

[
  {
    order_id: '570693702',
    id: '570693702',
    symbol: 'btcusd',
    exchange: 'gemini',
    avg_execution_price: '0.00',
    side: 'buy',
    type: 'exchange limit',
    timestamp: '1587396553',
    timestampms: 1587396553149,
    is_live: true,
    is_cancelled: false,
    is_hidden: false,
    was_forced: false,
    executed_amount: '0',
    client_order_id: 'SeOACkrjQ',
    options: [],
    price: '500.00',
    original_amount: '10',
    remaining_amount: '10'
  },
  {
    order_id: '570693699',
    id: '570693699',
    symbol: 'btcusd',
    exchange: 'gemini',
    avg_execution_price: '0.00',
    side: 'buy',
    type: 'exchange limit',
    timestamp: '1587396552',
    timestampms: 1587396552142,
    is_live: true,
    is_cancelled: false,
    is_hidden: false,
    was_forced: false,
    executed_amount: '0',
    client_order_id: 'BUZklEFFu',
    options: [],
    price: '400.00',
    original_amount: '10',
    remaining_amount: '10'
  },
  {
    order_id: '570693696',
    id: '570693696',
    symbol: 'btcusd',
    exchange: 'gemini',
    avg_execution_price: '0.00',
    side: 'buy',
    type: 'exchange limit',
    timestamp: '1587396551',
    timestampms: 1587396551142,
    is_live: true,
    is_cancelled: false,
    is_hidden: false,
    was_forced: false,
    executed_amount: '0',
    client_order_id: 'nZ8ViwbQa',
    options: [],
    price: '300.00',
    original_amount: '10',
    remaining_amount: '10'
  },
  {
    order_id: '570693690',
    id: '570693690',
    symbol: 'btcusd',
    exchange: 'gemini',
    avg_execution_price: '0.00',
    side: 'buy',
    type: 'exchange limit',
    timestamp: '1587396550',
    timestampms: 1587396550141,
    is_live: true,
    is_cancelled: false,
    is_hidden: false,
    was_forced: false,
    executed_amount: '0',
    client_order_id: 'qs3UEZgBP',
    options: [],
    price: '200.00',
    original_amount: '10',
    remaining_amount: '10'
  },
  {
    order_id: '570693582',
    id: '570693582',
    symbol: 'btcusd',
    exchange: 'gemini',
    avg_execution_price: '0.00',
    side: 'buy',
    type: 'exchange limit',
    timestamp: '1587396549',
    timestampms: 1587396549267,
    is_live: true,
    is_cancelled: false,
    is_hidden: false,
    was_forced: false,
    executed_amount: '0',
    client_order_id: 'lMX_fkJcL',
    options: [],
    price: '100.00',
    original_amount: '10',
    remaining_amount: '10'
  }
]
  1. Cancel your orders using cancelAllActiveOrders .

cancelAllActiveOrders()

This API call will cancel all your active orders.

Call:

restClient.cancelAllActiveOrders()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

{
  result: 'ok',
  details: {
    cancelledOrders: [ 570693702, 570693699, 570693696, 570693690, 570693582 ],
    cancelRejects: []
  }
}


After execution of cancelAllActiveOrders no orders will be shown in your browser.

  1. Using getMyActiveOrders get a list of all your active orders. Have they been cancelled?

Calling getMyActiveOrders again you can confirm if all your orders had been cancelled.

Call:

restClient.getMyActiveOrders()
.then(response => console.log(response))
.catch(error => console.log(error));

Reply:

[]

The reply shows an empty array. The absence of any order in this array shows us that all orders had been cancelled successfully.

2 Likes

Well I have to be honest here :sweat_smile: I think I have to do the DeFi 101 course first and do this course one more time. Anyway I have a good overview now and its a very interesting course.

I can’t use Gemini, so this is the equivalent on Kraken

const key          = 'Your key here'; // API Key
const secret       = 'your secret here'; // API Private Key
const KrakenClient = require('kraken-api'); // https://github.com/nothingisdead/npm-kraken-api
const kraken       = new KrakenClient(key, secret);
// I used the Async/await method

//1- getMyAvailableBalances
(async () => {
console.log(await kraken.api('Balance')); 
})();
//2- 5 limit sell orders (this is my real account so I can't buy anything I only have ADA at the moment.)
(async () => {
    var i;
    for (i = 0; i < 5; i++) {
    await kraken.api('AddOrder',{pair : 'ADAXBT', type:'sell',ordertype:'limit',price:5+i,volume:'1'});
    }
})();

//3- Get active orders
(async () => {
    var orders = await kraken.api('OpenOrders');
    var ordersKeys= Object.keys(orders.result.open);
    console.log(ordersKeys);
//4- Cancel all active orders
    if(ordersKeys){
        var i;
        for (i = 0; i < ordersKeys.length; i++) {
            try {
                await kraken.api('CancelOrder',{ txid: ordersKeys[i]}); 
            }
            catch (err) {
            console.log(err);
            }    
        }
    }
})();

Hi! I have the same problem as some other people - i can only do 2-3 buy orders simultaneously. The other 2 orders get error: “Nonce ‘123123’ has not increased since your last call to the Gemini API.”.

I’ve tried to change API keys and restarded Powershell but it doesnt seem to solve it. Do not know how to tackle this error.

Otherwise, everything seem logic and understandable.

// Anton

You get errors when placing multiple orders in a row. I used setTimeout with half a second and it works fine. I’m not sure if this is the most elegant approach.

const GeminiAPI = require("gemini-api").default;

var secret = "1123412341234123412342134";

var key = "account-123413324143214321334";


const restClient = new GeminiAPI({key, secret, sandbox:true});

setTimeout(function(){restClient.cancelAllActiveOrders()
  .then(response => console.log(response))
  .catch(error => console.log(error))},2000);


// order 10 ETH @ $10/ETH
setTimeout(function(){restClient.newOrder({amount:10, price:10, side:"buy", symbol:"ethusd"})
  .then(response => console.log(response))
  .catch(error => console.log(error))},500);

// order 10 BTC @ $10/BTC
setTimeout(function(){restClient.newOrder({amount:10, price:10, side:"buy", symbol:"btcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error))},500);

// order 100 lTC @ $1/lTC
setTimeout(function(){restClient.newOrder({amount:100, price:1, side:"buy", symbol:"ltcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error))},500);

// order 1 BTC @ $107/BTC
setTimeout(function(){restClient.newOrder({amount:1, price:107, side:"buy", symbol:"btcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error))},500);

// order 100 lTC @ $1/lTC
setTimeout(function(){restClient.newOrder({amount:100, price:1, side:"buy", symbol:"ltcusd"})
    .then(response => console.log(response))
    .catch(error => console.log(error))},500);



// see list of active orders
restClient.getMyActiveOrders()
    .then(response => console.log(response))
    .catch(error => console.log(error));

//Available balances
restClient.getMyAvailableBalances()
    .then(response => console.log(response))
    .catch(error => console.log(error));


1 Like

Hi Anton,
The exchange doesn’t like too many orders together. Try using setTimeout it worked for me:-

// order 10 ETH @ $10/ETH
setTimeout(function(){restClient.newOrder({amount:10, price:10, side:"buy", symbol:"ethusd"})
  .then(response => console.log(response))
  .catch(error => console.log(error))},500);

This with 500ms delay.

@ivan or @filip … I was able to work through all five questions with one issue. When I create an order, it will be either filled or cancelled. In other words, I’m not able to create an Open order using the Gemini API. I was able to complete the questions by creating an Open order on the Gemini Sandbox website and then used cancelAllActiveOrders. Everything seems to work fine except the ability to call an order that results in an Open Status. Do you know what could be causing this?

  • Here is a copy of my code:

const GeminiAPI = require(“gemini-api”).default;
const secret = “xxxxxx”;
const key = “account-xxxxx”;
const restClient = new GeminiAPI({key, secret, sandbox:true});

restClient.newOrder({amount:2.5,price:5500,side:“buy”,symbol:“btcusd”})
.then(response => console.log(response))
.catch(error => console.log(error));

  • It appears to look correct when executed:

image

  • However, the Status shows up as ‘Canceled’ in Gemini. Stated another way, I have not yet been able to create an Open order using the API.

That was how I got around the nonce issue. :sunglasses:
I found nesting my setTimeout calls worked for me.

// trimmed out GeminiAPI initialization
// my GeminiAPI (index.js) has been patched with "account: 'primary'" added to the privateInvocation params to get around API error: missingAccounts

var bid;
var errHandler = function(err) {
    console.log(err);
}

//Get balances and print to console
console.log("\n\nBegin with balances...");
restClient.getMyAvailableBalances().then(response => console.log(response)).catch(console.error);

//Get latest bid price for use in newOrder calls
restClient.getTicker('btcusd').then(function (res){bid = res.bid; return bid;}, errHandler).catch(console.error);

//Nested setTimeout calls to allow each newOrder (promise) to fulfill before moving on
setTimeout(() => {
  console.log("\nNow that we got a recent bid price...");
  console.log("\n1.Buying 1 BTC @ $" + bid);
  restClient.newOrder({amount: 1, price: bid, side: "buy", symbol: "btcusd" }).then(response => console.log(response)).catch(console.error);
  setTimeout(() => {
    console.log("\n2.Buying 2 BTC @ $" + (bid-60));
    restClient.newOrder({amount: 2, price: (bid-60), side: "buy", symbol: "btcusd" }).then(response => console.log(response)).catch(console.error);
    setTimeout(() => {
      console.log("\n3.Buying 3 BTC @ $" + (bid-100));
      restClient.newOrder({amount: 3, price: (bid-100), side: "buy", symbol: "btcusd" }).then(response => console.log(response)).catch(console.error);
    }, 600);
  }, 600);
}, 600);

//Nested setTimeout calls timed to start sometime (4sec), aiming to start after the newOrder batch is done
setTimeout(() => {
  console.log("\n");
  restClient.getMyActiveOrders().then(response => console.log(response));
  console.log("\n4.Time to take out the trash...");
  setTimeout(() => {
    restClient.cancelAllActiveOrders().then(response => console.log(response));
    console.log("\n5.Let's see if we did our job and cleared it up...");
    setTimeout(() => {
      restClient.getMyActiveOrders().then(response => console.log(response));
      console.log("\n6.Did we clear it?");
    }, 600);
  }, 600);
}, 4000);

I was initially trying to figure a solution that would wait for each call to complete before making a new one, such as @Ouzo69 mentioned but didn’t endure that option long enough and instead got lucky with setTimeout(). Cheers @Ouzo69 for that implementation of await sleep(n);

If you used a price that was between bid and ask (such that it could be filled with current orders in the orderbook) then your order would instantly fill. If you submit a newOrder with price that’s significantly far away from bid/ask prices it will not accept the order.

Try using a price that’s less distant from the spot price. Such as (bid-100) or (last-150) as examples.