Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions configuration/coinbase.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@
"WITHDRAW_ONE": ""
},
"PREFIX": "",
"SOCKET": {
"CHANNEL": {
"CANDLES": "candles",
"LEVEL2": "level2",
"MARKET_TRADES": "market_trades",
"STATUS": "status",
"TICKER_BATCH": "ticker_batch",
"TICKERS": "ticker",
"USER": "user"
},
"SUBSCRIBE": "subscribe",
"UNSUBSCRIBE": "unsubscribe",
"URL": "wss://advanced-trade-ws.coinbase.com"
},
"TRADE": {
"BUY": "BUY",
"SELL": "SELL"
Expand Down
23 changes: 19 additions & 4 deletions lib/fetch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { blind } from "./authentication.mjs";
import { dirObject } from "./output.mjs";
import { toPascalCase } from "./string.mjs";

export const fetchData = (method, url, data, headers) => {
const headerAuth = headers.Authorization,
Expand Down Expand Up @@ -34,10 +35,14 @@ export const fetchData = (method, url, data, headers) => {

return global.fetch(url, options)
.then(async (response) => {
const timeEnd = Date.now()

global.clearInterval(intervalId);
process.stdout.write(`\rFetching done in ${timeEnd - timeStart} ms.\n`);

const timeEnd = Date.now(),
done = ` done in ${timeEnd - timeStart} ms.`,
eraser = " ".repeat(intervalCount > done.length ? intervalCount - done.length : 0);

process.stdout.write(`\rFetching${done}${eraser}\n`);
dirObject("Response headers", entryHeaders(response));

const text = await response.text();
let json;
Expand All @@ -57,9 +62,19 @@ export const fetchData = (method, url, data, headers) => {
json,
status: response.status,
statusText: response.statusText
}
}
})
},
entryHeaders = (response) => {
const entries = response.headers.entries(),
/**
* Create new shallow-copied Array instance from Headers iterator object to map lower cased header keys.
*/
array = Array.from(entries, ([key, value]) => [toPascalCase(key), value]),
headers = Object.fromEntries(array)

return headers
},
stringifyQuery = (data) => {
const query = Object.keys(data).length ? "?" + String(new URLSearchParams(data)) : "";

Expand Down
23 changes: 18 additions & 5 deletions lib/utility.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,44 @@ export const obtainName = (path, pathAll) => {
}

/**
* Parse process argument vectors to usable parameters.
* Parse process argument vectors to usable handler, parameters and options.
*/
export const parseArguments = () => {
const options = {};
let args = process.argv.slice(3).map(param => {
/** Param with defined value. */
if (param.includes("=")) {
const [key, value] = param.split("=");

return { [key]: value };
}
/* if (!global.apiTools) {
global.apiTools = {}
} */
if (param.includes("--head")) {
/** @todo Resolve side effect. */
options.isHeaders = true
}
if (param.includes("--snap")) {
global.isSnapshot = true
/** @todo Resolve side effect. */
options.isSnapshot = true
}

return param
}),
/** Params without a value. */
params = args.filter(
arg => typeof arg === "string" && arg !== "--snap"
arg => typeof arg === "string" && !arg.includes("--")
),
options = args.reduce((accum, arg) =>
/** Params with defined value. */
paramsDefined = args.reduce((accum, arg) =>
typeof arg === "object" ? Object.assign(accum, arg) : accum
, {});

if (Object.keys(options).length) params.push(options);
if (Object.keys(paramsDefined).length) params.push(paramsDefined);
args = {
handler: process.argv[2],
options,
params
};
dirObject("arguments", args);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "api-tools",
"version": "0.1.1-alpha",
"version": "0.1.3-alpha",
"description": "Tools to work with a REST API.",
"main": "request.mjs",
"directories": {
Expand Down
16 changes: 14 additions & 2 deletions request/bybit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
* Request Bybit API spot endpoints.
*
* @module request/bybit
*
* Each account can have up to 20 keys.
* API keys that are bound to IP addresses are permanently valid.
* API keys that are not bound to IP addresses are valid for 3 months.
* After changing login password API keys will expire within 7 days.
* API keys generated by the Bybit operates with HMAC encryption: you will be provided with a pair of public and private keys.
* Self-generated API keys operate with RSA encryption: user creates them by the software and only provides the public key.
* Withdrawal permission requires IP restriction.
* @see https://www.bybit.com/app/user/api-management
* @see https://www.bybit.com/app/user/add-secret-key?type=auto
* @see https://www.bybit.com/app/user/add-secret-key?type=system
* @see https://www.bybit.com/user/assets/money-address
*/

import config from "../configuration/bybit.json" with { type: "json" };
Expand Down Expand Up @@ -56,9 +68,9 @@ const {
}
} = settings,
requestBybit = () => {
const { handler, params } = parseArguments();
const { handler, options, params } = parseArguments();

global.apiTools = { config, settings };
global.apiTools = { config, options, settings };
if (handler) {
switch (handler) {
case "balanceAll": return balanceAll(...params);
Expand Down
1 change: 1 addition & 0 deletions request/bybit/account/wallets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import bybitGet from "../get.mjs";
import bybitValidate from "../validate.mjs";

/**
* `memberId` is UID.
* @see https://bybit-exchange.github.io/docs/v5/user/wallet-type
* @see https://bybit-exchange.github.io/docs/v5/enum#accounttype
*/
Expand Down
1 change: 1 addition & 0 deletions request/bybit/balance/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import bybitGet from "../get.mjs";
import bybitValidate from "../validate.mjs";

/**
* `memberId` is UID.
* @see https://bybit-exchange.github.io/docs/v5/asset/balance/all-balance
*/
const balanceAll = (accountType, memberId, {
Expand Down
1 change: 1 addition & 0 deletions request/bybit/balance/one.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import bybitGet from "../get.mjs";
import bybitValidate from "../validate.mjs";

/**
* `memberId` is UID.
* @see https://bybit-exchange.github.io/docs/v5/asset/balance/all-balance
*/
const balanceOne = (accountType, coin, memberId, {
Expand Down
104 changes: 29 additions & 75 deletions request/bybit/order/limit-buy.mjs
Original file line number Diff line number Diff line change
@@ -1,92 +1,46 @@
/**
* Bybit API order place limit endpoint.
* Handle Bybit API place order limit buy endpoint.
*
* @module request/bybit/order/place_limit
* @module request/bybit/order/limit-buy
*/

import config from "../../../configuration/bybit.json" with { type: "json" };
import settings from "../../../settings/bybit.json" with { type: "json" };
import { throwRequired, warnOptional } from "../../../lib/output.mjs";
import bybitPost from "../post.mjs";

const {
CURRENCY,
ORDER,
PATH: {
ORDER_PLACE
},
TRADE,
} = config,
{
account: {
category,
},
authentication: {
sign
},
currency: {
base,
quote
}
} = settings;
import bybitValidate from "../validate.mjs";

/**
* `qty` is in a base currency.
* @see https://bybit-exchange.github.io/docs/v5/enum#smptype
* @see https://bybit-exchange.github.io/docs/v5/order/create-order
* @see https://bybit-exchange.github.io/docs/v5/smp
*/
const orderLimitBuy = (qty, price, symbol, {
closeOnTrigger, isLeverage, marketUnit, mmp, orderFilter, orderIv, orderLinkId, positionIdx, reduceOnly,
slLimitPrice, slOrderType, slTriggerBy, smpType, stopLoss, takeProfit, timeInForce, tpLimitPrice,
tpOrderType, tpTriggerBy, tpslMode, triggerDirection, triggerPrice, triggerBy
} = {}) => {
const data = {
category,
// closeOnTrigger,
// isLeverage,
// marketUnit,
// mmp,
// orderFilter,
// orderIv,
// orderLinkId,
orderType: ORDER.LIMIT,
// positionIdx,
// price,
// qty,
// reduceOnly,
side: TRADE.BUY,
// slLimitPrice,
// slOrderType
// slTriggerBy,
smpType: "None",
// stopLoss,
symbol: base + quote
// takeProfit,
// timeInForce,
// tpLimitPrice,
// tpOrderType,
// tpTriggerBy,
// tpslMode,
// triggerDirection,
// triggerPrice,
// triggerBy,
};

if (Number(price))
data.price = price
else throwRequired(PATH, ORDER_PLACE, "price");
if (Number(qty))
data.qty = qty
else throwRequired(PATH, ORDER_PLACE, "qty");
if (symbol) {
if (
Object.values(CURRENCY.BASE).some(currency1 =>
Object.values(CURRENCY.QUOTE).some(currency2 =>
(currency1 + currency2 === symbol) && currency1 !== currency2
)
)
) {
data.symbol = symbol
} else warnOptional(PATH, ORDER_PLACE, "symbol", data.symbol);
}
const { config, settings } = global.apiTools,
{ ORDER, PATH: { ORDER_PLACE }, TRADE } = config,
{
account,
authentication: { sign },
currency: { base, quote }
} = settings,
defaults = {
category: account.category,
orderType: ORDER.LIMIT,
side: TRADE.BUY,
// smpType: "None",
symbol: base + quote
},
data = bybitValidate(ORDER_PLACE, defaults,
{ throwRequired: { price, qty } },
{ warnOptional: { symbol } },
{ warnRequired: {
closeOnTrigger, isLeverage, marketUnit, mmp, orderFilter, orderIv, orderLinkId, positionIdx, reduceOnly,
slLimitPrice, slOrderType, slTriggerBy, smpType, stopLoss, takeProfit, timeInForce, tpLimitPrice,
tpOrderType, tpTriggerBy, tpslMode, triggerDirection, triggerPrice, triggerBy
} },
);

return bybitPost(sign, ORDER_PLACE, data)
};
Expand Down
Loading