Working in the sandbox with a trading robot on the Moscow Exchange
Before using a trading robot with real money, you want to test everything on a demo account (or “sandbox”). This is when software errors do not have much cost.
I plan to use the robot on the Moscow Exchange, through the API of one of the brokers. To start trading on the stock exchange, a private investor needs a brokerage account. However, the minimum number of Russian brokers have their own APIs (at the moment I only know FINAM, Alor, Tinkoff Investments). For subjective reasons, I chose to work with the T‑Bank Invest API (formerly Tinkoff) through the JavaScript runtime environment Node.JS.
In this article I explain how to use a sandbox:
Open an account.
Top up your account balance with rubles through a special request.
View all your open accounts in the sandbox.
Buy 1 share.
Sell 1 share.
Get all open positions of the specified account.
Close account.
SilverFir-TradingBot\src\sandbox.js
This Node.js code interacts with the Tinkoff Invest APIallowing you to simulate trading operations on a virtual account, which allows you to test some API functions manually. Here's what this code does:
1. Import modules
secrets
: Imports access keys and identifiers from an external configuration file (secrets
), which helps protect confidential information.logger
: Imports a logging module that writes logs to a file or console. This is important for tracking bot activity and debugging.logFunctionName
: Imports a utility to get function names, making it easy to log the current function context.TinkoffClient
: imports a client module to interact with the Tinkoff Invest API. This client processes API requests.
2. Client setup
API_TOKEN
: Gets an API token (in sandbox mode) from an external configuration file (secrets
) for authentication.tinkoffClient
: creates an instanceTinkoffClient
with the sandbox token, setting up the API communication for the sandbox environment.
3. Sandbox functions
sandboxAccount()
: This is a basic function demonstrating various sandbox account operations, with a few actions currently commented out.logFunctionName()
: Logs the function name in the console, which is useful for tracking in complex applications.GetSandboxAccounts
: Retrieves all open positions of the specified account.
Commented out operations:
OpenSandboxAccount: Registers a new account in the sandbox, allowing you to start testing again.
SandboxPayIn: credits funds to the sandbox account in Russian rubles (RUB). Here the indicated amount is 30,000 rubles.
CloseSandboxAccount: Closes the specified sandbox account using it
accountId
allowing you to reset after testing.GetSandboxPositions: Retrieves and records all open positions for the specified account ID.
placeMarketOrder: sends market orders to buy and sell the specified instrument (here
BBG004730N88
). This will allow you to test the functionality of placing orders in the sandbox.
Errors
This code structure demonstrates how to interact with a virtual trading account in the Tinkoff API. Commented out code blocks indicate additional functions that can be activated if necessary, such as opening, funding and closing sandbox accounts, as well as placing buy/sell orders.
// Импорт необходимых модулей
const secrets = require('../config/secrets'); // Ключи доступа и идентификаторы
const logger = require('./services/logService'); // Логирование в файл и консоль
const logFunctionName = require('./services/logFunctionName'); // Получение имени функции
const TinkoffClient = require('./grpc/tinkoffClient'); // модуль для взаимодействия с API Tinkoff Invest
const API_TOKEN = secrets.TbankSandboxMode;
const tinkoffClient = new TinkoffClient(API_TOKEN);
async function sandboxAccount() {
// https://tinkoff.github.io/investAPI/swagger-ui/#/SandboxService/SandboxService_GetSandboxAccounts
logger.info(`Запуск функции ${JSON.stringify(logFunctionName())}\n`);
// // Регистрации счёта в песочнице
// const OpenSandboxAccount = await tinkoffClient.callApi('SandboxService/OpenSandboxAccount');
// logger.info(`Регистрации счёта в песочнице:\n ${JSON.stringify(OpenSandboxAccount, null, '\t')}\n\n`);
// // Пополнение баланса счёта песочницы
// const RUB = {
// "accountId": secrets.AccountID,
// "amount": {
// "nano": 0, // Дробная часть отсутствует
// "currency": "RUB",
// "units": 30000, // Сумма в рублях
// }
// };
// const SandboxPayIn = await tinkoffClient.callApi('SandboxService/SandboxPayIn', RUB);
// logger.info(`Пополнение баланса счёта песочницы:\n ${JSON.stringify(SandboxPayIn, null, '\t')}\n\n`);
// // Закрытие счёта в песочнице
// const accountId = {
// "accountId": secrets.AccountID
// };
// const CloseSandboxAccount = await tinkoffClient.callApi('SandboxService/CloseSandboxAccount', accountId);
// logger.info(`Закрытие счёта в песочнице:\n ${JSON.stringify(CloseSandboxAccount, null, '\t')}\n\n`);
// Посмотреть счета в песочнице
const GetSandboxAccounts = await tinkoffClient.callApi('SandboxService/GetSandboxAccounts');
logger.info(`Список счетов в песочнице:\n ${JSON.stringify(GetSandboxAccounts, null, '\t')}\n\n`);
// // Получить все открытые позиции указанного счёта
// const accountId = {
// "accountId": secrets.AccountID
// };
// const GetSandboxPositions = await tinkoffClient.callApi('OperationsService/GetPositions', accountId);
// logger.info(`Все открытые позиции счёта ${secrets.AccountID}:\n ${JSON.stringify(GetSandboxPositions, null, '\t')}\n\n`);
// // Функция для отправки рыночного ордера
// tinkoffClient.placeMarketOrder('BBG004730N88', 1, 'ORDER_DIRECTION_BUY'); // Купить 1 акцию
// tinkoffClient.placeMarketOrder('BBG004730N88', 1, 'ORDER_DIRECTION_SELL'); // Продать 1 акцию
}
// ======================================================================================
// ============ Запуск функций ===================================================
// ======================================================================================
sandboxAccount().catch(logger.error);
Fast action
I wasn't expecting any particularly fast performance. For a human this is very fast, but for a robot it is slow. This will have to be taken into account when developing a trading strategy.
[Running] node "d:\Synology ...\SilverFir-TradingBot_github\src\sandbox.js"
2024-11-01 14:11:57 [INFO]: Запуск функции "sandboxAccount"
2024-11-01 14:11:58 [WARN]: Операция продажи выполнена успешно для Сбер Банк (SBER) (BBG004730N88).
2024-11-01 14:11:58 [INFO]: Детали операции:
{
"orderId": "27a35903-2134-4aaf-XXXX-3b38bc38c5e5",
"executionReportStatus": "EXECUTION_REPORT_STATUS_FILL",
"lotsRequested": "1",
"lotsExecuted": "1",
"initialOrderPrice": {
"currency": "rub",
"units": "2358",
"nano": 100000000
},
"executedOrderPrice": {
"currency": "rub",
"units": "235",
"nano": 810000000
},
"totalOrderAmount": {
"currency": "rub",
"units": "2358",
"nano": 100000000
},
"initialCommission": {
"currency": "rub",
"units": "1",
"nano": 179050000
},
"executedCommission": {
"currency": "rub",
"units": "1",
"nano": 179050000
},
"figi": "BBG004730N88",
"direction": "ORDER_DIRECTION_SELL",
"initialSecurityPrice": {
"currency": "rub",
"units": "235",
"nano": 810000000
},
"orderType": "ORDER_TYPE_MARKET",
"message": "",
"initialOrderPricePt": {
"units": "0",
"nano": 0
},
"instrumentUid": "e6123145-9665-43e0-XXXX-cd61b8aa9b13",
"orderRequestId": "",
"responseMetadata": {
"trackingId": "d059748a138038d3XXXXX93783d61a99",
"serverTime": "2024-11-01T09:11:57.919185435Z"
}
}
2024-11-01 14:11:58 [INFO]: Идентификатор продажи: 27a35903-2134-4aaf-XXXX-3b38bc38c5e5.
2024-11-01 14:11:58 [INFO]: Общая стоимость сделки: 2358.1 руб.
2024-11-01 14:11:58 [INFO]: Цена за 1 шт. Сбер Банк (SBER): 235.81 руб.
2024-11-01 14:11:58 [INFO]: Комиссия за сделку: 1.17905 руб.
[Done] exited with code=0 in 1.146 seconds
For a trading robot, 1.146 seconds from sending an order to its execution can be considered a rather slow time.
In high-frequency trading (HFT), where companies compete for sub-millisecond execution times, order processing times of more than one second will be prohibitively long. HFT strategies are based on executing thousands of trades in a fraction of a second, so 1.146 seconds would make this robot uncompetitive.
In contrast, for a long-term strategy such as a day trading bot or swing trading, this time may be acceptable. Execution speed remains important, but not as critical as in HFT. In these cases, the trade-off often leans towards reliability and cost-effectiveness rather than pure speed. A 1 second delay will generally not undermine profitability in a strategy where trades are executed minutes or even hours apart.
I plan to use swing trading, which is a trading strategy focused on capturing short- and medium-term price movements, usually holding assets for a few days or a few weeks. The goal is to profit from price “swings” by taking advantage of market momentum as prices fluctuate within a trend or between support and resistance levels.
Results
The project is fully presented on GitHub: https://github.com/empenoso/SilverFir-TradingBot.
New modules will be uploaded as they are written and tested.
Author: Mikhail Shardin
November 5, 2024