Прочитал, что появился интерес к ботописательству для биржи Голоса. Вот хочу поделиться своими наработками. Это не "кнопка бабло" или "грааль". Это просто пример бота, который помогает выставлять ордера на бирже согласно простого алгоритма. В двух словах - Вы купили на бинансе, или где-то еще, возможно даже в этом же стакане некоторую сумму usdt, допустим по цене 73 рубля за штуку, и хотим их продать, чтобы заработать какую-нибудь копеечку. Для "эталона цены" выбираем биржу бинанс, как наиболее ликвидную на сегодняшний день. И выставляем ордера лесенкой с начальным профитом в 1 процент и шагом в 0.5 процента в количестве 10 штук. Все параметры настраиваются, комментарии в коде есть.
Для бота используются прямые вызовы API ноды Голоса по https протоколу, а из "официальной" библиотеки используется только функция подписания транзакций.
Бота можно использовать в чистом виде - ордера он выставляет :) но нужно в Силу Голоса добавить N-е количество голосов, иначе боту просто "перекроют кислород" (не дадут выставлять ордера). Либо взять за основу и написать свой алгоритм, а из бота только использовать код для выставления-удаления ордеров и тп.
Бота можно запускать из cron раз в 5 минут или около того, либо переписать код так, чтобы он выполнялся в цикле с небольшой задержкой.
Ну и непосредственно сам код.
const { Spot } = require('@binance/connector');
const golos = require('golos-classic-js');
const request = require('sync-request');
// если у вас нет api ключей, то просто оставить их пустыми,
// для запроса цены регистрация не нужна
const apiKey = ''
const apiSecret = ''
const client = new Spot(apiKey, apiSecret)
// минимальная цена для продажи "в безубыток"
const min_price = 73.00;
const account = 'acc-b0t'; // аккаунт для бота
const wif = '5...'; // активный приватный ключ
// нода голоса для работы
const host = 'api-golos.blckchnd.com';
// количество выставляемых ордеров
const orders_count = 10;
// начальный "профит"
const first_step = 1.01;
// шаг приращения цены
const order_step = 0.005;
// цена "с бинанса"
var price = 0;
//============================================================================
async function main() {
// получаем с бинанса цену USDT в рублях
let resp = await client.avgPrice(symbol='USDTRUB');
price = resp.data.price;
console.log(price);
if (price >= min_price) {
// проверяем открытые ордера
let result = getOpenOrders(host, account, ["YMUSDT", "YMRUB"]);
console.log(result);
if (result != '') {
console.log('есть открытые ордера');
let i = 0;
while (result[i] != undefined) {
let orderid = result[i].orderid;
console.log(orderid);
let trx_id = doLimitOrderCancel(host, wif, account, orderid);
console.log(trx_id);
i++;
}
doCreateOrders();
}
else {
console.log('нет открытых ордеров');
doCreateOrders();
}
return;
}
}
//============================================================================
function doCreateOrders() {
// получаем количество USDT на счету
let acc_result = getAccountBalances(host,account);
let amount = acc_result[0].YMUSDT.balance;
let amountArray = amount.split(" ");
let summa = amountArray[0];
let token = amountArray[1];
let i = 0;
while (i < orders_count) {
let orderid = Math.floor(Date.now() / 1000);
let expiration = new Date();
expiration.setDate(362);
expiration = expiration.toISOString().substr(0, 19);
let order_size = summa / orders_count;
let usdt = order_size.toFixed(6) + " YMUSDT"
let ymrub = parseFloat(order_size * price * (first_step+i*order_step)).toFixed(2) + " YMRUB";
let trx_id = doLimitOrderCreate(host, wif, account, orderid, usdt, ymrub, false, expiration);
console.log(trx_id);
i++;
}
}
//============================================================================
function getAccountBalances(host, account) {
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_accounts_balances",[["'+account+'"]]]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
console.log(json);
let result = json.result;
return (result);
}
//============================================================================
function getOpenOrders(host, owner, pair) {
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["market_history","get_open_orders",["' + owner + '",' + JSON.stringify(pair) +']]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
//console.log(json);
let result = json.result;
return (result);
}
//============================================================================
function doLimitOrderCancel(host, wif, account, orderid) {
let head_block_number = get_head_block_num(host);
let ref_block_num = (head_block_number - 3) & 0xffff;
let var1 = head_block_number - 2;
block_header = get_block_header(host, var1);
let ref_block_prefix = new Buffer(block_header.previous, 'hex').readUInt32LE(4)
const now = new Date().getTime() + 120 * 1000;
const expiration = new Date(now).toISOString().split('.')[0]
let ops = [];
ops.push(["limit_order_cancel",
{
'owner' : account,
'orderid' : orderid
}]);
const unsigned_trx = {
'expiration': expiration,
'extensions': [],
'operations': ops,
'ref_block_num': ref_block_num,
'ref_block_prefix': ref_block_prefix
}
let signed_trx = null;
try {
signed_trx = golos.auth.signTransaction(unsigned_trx,{"active":wif})
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["network_broadcast_api","broadcast_transaction_synchronous",[{"ref_block_num":' + ref_block_num +',"ref_block_prefix":'+ref_block_prefix+',"expiration":"'+expiration+'","operations":[["limit_order_cancel",{"owner":"'+account+'","orderid":'+orderid+'}]],"extensions":[],"signatures":["'+signed_trx.signatures+'"]} ]]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
console.log(json);
let transaction = json.result;
return (transaction.id);
}
catch (error) {
console.warn("Не удалось подписать или отправить транзакцию: " + error.message);
return 0;
}
}
//============================================================================
function doLimitOrderCreate(host, wif, account, orderid, amount_to_sell, min_to_receive, fill_or_kill, exp) {
let head_block_number = get_head_block_num(host);
let ref_block_num = (head_block_number - 3) & 0xffff;
let var1 = head_block_number - 2;
block_header = get_block_header(host, var1);
let ref_block_prefix = new Buffer(block_header.previous, 'hex').readUInt32LE(4)
const now = new Date().getTime() + 120 * 1000;
const expiration = new Date(now).toISOString().split('.')[0]
let ops = [];
ops.push(["limit_order_create",
{
'owner' : account,
'orderid' : orderid,
'amount_to_sell' : amount_to_sell,
'min_to_receive' : min_to_receive,
'fill_or_kill' : fill_or_kill,
'expiration' : exp
}]);
const unsigned_trx = {
'expiration': expiration,
'extensions': [],
'operations': ops,
'ref_block_num': ref_block_num,
'ref_block_prefix': ref_block_prefix
}
let signed_trx = null;
try {
signed_trx = golos.auth.signTransaction(unsigned_trx,{"active":wif})
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["network_broadcast_api","broadcast_transaction_synchronous",[{"ref_block_num":' + ref_block_num +',"ref_block_prefix":'+ref_block_prefix+',"expiration":"'+expiration+'","operations":[["limit_order_create",{"owner":"'+account+'","orderid":'+orderid+',"amount_to_sell":"'+amount_to_sell+'","min_to_receive":"'+min_to_receive+'","fill_or_kill":'+fill_or_kill+
',"expiration":"'+exp+'"}]],"extensions":[],"signatures":["'+signed_trx.signatures+'"]} ]]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
console.log(json);
let transaction = json.result;
return (transaction.id);
}
catch (error) {
console.warn("Не удалось подписать или отправить транзакцию: " + error.message);
return 0;
}
}
//============================================================================
function get_head_block_num (host) {
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[] ]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
let properties = json.result;
return (properties.head_block_number);
}
//============================================================================
function get_block_header(host, param) {
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_block_header",["' + param + '"] ]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
let block_header = json.result;
return (block_header);
}
function getAccount(host, account) {
let res = request('POST', 'https://' + host + '/', {
headers: {'content-type': 'text/plain' },
body: '{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_accounts",[["'+account+'"]]]}'
});
let chunk = res.body;
let textChunk = chunk.toString('utf8');
let json = JSON.parse(textChunk);
let properties = json.result;
return (properties);
}
main();