Tinkoff invest api
Tinkoff invest api

Введение

Количество физических лиц, имеющих брокерские счета на Московской бирже, за июль 2021 года увеличилось на 446 тыс. человек, достигнув 13,2 млн. Ими открыто 21,6 млн брокерских счетов. В июле 2021 года сделки на бирже совершали более 1,9 млн человек. (Московская биржа)

На фоне снижения ключевой ставки и ввода налога на доходы с депозитов физических лиц, у Россиян появился нешуточный интерес к инвестициям. Не обошел данный тренд и меня, не могу назвать себя новичком в торговле на фондовом рынке, в различные периоды своей жизни мне довелось воспользоваться услугами таких брокеров, как АТОН, ВТБ, ОТКРЫТИЕ, РСХБ, и наконец, ТИНЬКОФФ.

Иррациональный выбор

Суть отношения состоятельных людей к деньгам — отнюдь не в экономии или рациональном использовании. На одной «экономии» состояния не построишь. (Дмитрий Васильевич Брейтенбихер – российский банкир и финансист)

Сравнивая тарифы брокеров, несложно прийти к заключению, что с точки зрения экономической рациональности, ТИНЬКОФФ нам совсем не бро и если вы не относите себя к премиальным клиентам (на счетах от 3 млн.), то тарифы могут заставить плакать и смеяться одновременно. Так почему многие выбирают ТИНЬКОФФ? За всю Одессу Россию говорить не буду, лично мне, программисту по специальности, было очень интересно узнать что же за зверь такой TINKOFF INVEST API и насколько он подходит для автоматизации торговли и анализа данных. Ну ведь не допотопными QUIK и MetaQuote с их конструкциями из костылей на LUA и MQL пользоваться в 21 веке?

Задачи и инструменты

Дайте маленькому мальчику молоток, и он обнаружит, что по всем окружающим предметам просто необходимо стукнуть. (Авраам Каплан – американский философ)

Самое время определиться с инструментарием. Изначально была мысль создать SPRING-проект с API, СУБД, планировщиком, и прочим. Да что идея? Большую часть из этого я реализовал, но быстро пришел к заключению, что описание готового проекта – это не совсем то, чего ждет аудитория хабра. Пришла идея идти от простого – к сложному, от малого – к великому. Публикуя информацию о проделанной работе частями, можно анализировать мнения читателей и притворять в жизнь их пожелания. И как только я сформулировал все это в голове, пришел к выводу, что я "изобрел" Agile-манифест, уж больно похоже. Об Agile и моем отношению к нему читайте чуть ниже.

ЗАДАЧИ

Планируется разработать программный комплекс, который позволит собирать, хранить и анализировать данные, на основе полученной в результате анализа информации, в соответствии с заложенными в систему стратегиями, будут формироваться информационные сообщения и осуществляться торговые операции.

ИНСТРУМЕНТЫ

  • TINKOFF INVEST API – получение информации, торговые операции;

  • POSTGRE SQL – СУБД для хранения информации;

  • SPRING – фреймворк для формирования API взаимодействия с внешними системами, разграничения прав доступа к ресурсам, манипулирования данными;

  • TA4J – библиотека для анализа данных;

  • JFREECHART – библиотека для построения графиков и диаграмм;

  • JSOUP – библиотека парсинга сторонних сайтов для получения дополнительной информации (календари, отчеты, графики выплаты дивидендов, и т.п.);

  • TELEGRAM API – отправка сообщений, интерфейс для управления.

Как я упоминал ранее, данный список не будем принимать за константу, все может измениться в ходе реализации проекта и получения обратной связи от читателей. Гибкий подход.

Гибкий подход (Agile)

Нет времени объяснять! Суй в жопу ананас! (Интернет-фольклор)

Что будет из себя представлять готовый продукт? Какие требования мы выставляем к приложению? Эти вопросы задет себе каждый владелец продукта, менеджер, архитектор, тимлид или разработчик. И иногда случается так, что общая концепция вроде бы и понятна, но нет никакой конкретики, и что делать? В этом случае на помощь могут прийти, так называемые, "гибкие" (Agile) методологиями разработки, де-факто данный подход стал одним из отраслевых стандартов проектного управления и разработки программного обеспечения. Если коротко, то суть заключается в том, что заказчик может внести новые требования на любом этапе реализации проекта. Насколько этот метод универсален можно и нужно спорить.

Моя профессиональная область – это разработка программного обеспечения для банков, где Agile, с легкой подачи Германа Оскаревича прописался всерьез, и похоже, надолго. Если читатель спросит мое личное отношение к данному явлению, то скажу, что словом "Agile" хорошо прикрывать недостаток вовлеченности заинтересованных лиц при подготовке к реализации проекта, а именно формировании требований и проектирования. Agile'ом вполне обоснованно можно замаскировать любой бардак, в том числе, творящийся в головах участников команды :).

Дочитав до этого абзаца, любители Agile , должно было, успели на меня обидеться. Не не стоит! Моя характеристика - это всего лишь выводы из личного травмирующего опыта, при этом жизнь гораздо богаче. Сходу могу привести несколько примеров, когда именно Agile позволяет решать задачи, получая ощутимое преимущество в сравнении с традиционными методами управления проектами, минимизируя при этом временные, трудовые и денежные затраты. Внедрение новых программных комплексов в существующую среду, прототипирование и проверка теорий, разработка новых продуктов с нечетко сформулированными требованиями, вывод продукта на рынок в кратчайшие сроки, в решении подобных задач использование гибких методологий может гораздо быстрее привести команду к финальному результату. Попытка же использовать Agile просто по причине инновационности подхода, сравнима с желанием сунуть в жопу ананас, не задаваясь при этом вопросом "нафига, а главное, зачем?".

Как вы поняли, концепция обрисована крупными мазками. Я говорю: "перед нами", ожидая, что аудитория хабра поможет с формулированием требований к системе и в выборе оптимальных инструментов для реализации задуманных идей. Если есть мысли по теме статьи или конструктивная критика, пишите в комментариях, ваше мнение окажет неоценимую поддержку в развитии проекта.

На этом про Agile все, если вы зашли только ради этого, то ничего интересного для вас, увы, в данной статье больше не будет.

Подготовительный этап

Только тот, кто тщательно подготовился, имеет возможность импровизировать. (Эрнст Ингмар Бергман – шведский режиссёр театра и кино, сценарист, писатель)

Не имея ни малейшего желания в очередной раз пересказывать то, о чем много раз говорено, приведу ссылки, по которым вы найдете исчерпывающую информацию о возможностях API, остается немного сожалеть о том, что в TINKOFF упорно не замечают, что на их ресурсах размещена ссылка на нерабочий ресурс, тревожный звоночек...

Получаем токены TINKOFF-INVEST

Получение токена
  1. Зайдите в свой аккаунт на https://tinkoff.ru/

  2. Перейдите в раздел инвестиций

  3. Перейдите в настройки

  4. Функция "Подтверждение сделок кодом" должна быть отключена

  5. Выпустите токен OpenAPI для биржи и Sandbox. Возможно, система попросит вас авторизоваться еще раз. Не беспокойтесь, это необходимо для подключения робота к торговой платформе.

  6. Скопируйте токен и сохраните его. Токен отображается только один раз, просмотреть его позже не получится. Тем не менее вы можете выпускать неограниченное количество токенов.

https://tinkoffcreditsystems.github.io/invest-openapi/auth/

Создание подключения

Путь в тысячу ли начинается с первого шага. (Лао-цзы – древнекитайский философ VI—V веков до н.э.)

Задачи сформулированы, токены получены, самое время приступить к разработке. Для начала нам необходимо понять с какими инструментами работает TINKOFF API. На основе анализа полученной информации можно будет двигаться дальше.

Итак, создаем новый проект, в моем случае проект получил название "tinkoffinvest". Для сборки проекта и управления зависимостями я использую maven, также я применяю плагин компилятора lombok, который путем преобразования аннотаций в код, упрощает написание приложения, делая его более лаконичным.

Первым делом нам необходимо добавить зависимости для работы с TINKOFF INVEST API:

  • openapi-java-sdk-core – набор инструментов для разработки, содержит интерфейсы всех частей REST API и Streaming API, а также модели данных, которые они используют;

  • openapi-java-sdk-java8 – набор инструментов для разработки, содержит реализацию core-интерфейсов с использованием http-клиента из библиотеки OkHttp;

  • okhttp – библиотека для работы c http-протоколом.

Зависимости
    <dependencies>
        <dependency>
            <groupId>ru.tinkoff.invest</groupId>
            <artifactId>openapi-java-sdk-core</artifactId>
            <version>0.5.1</version>
        </dependency>
        <dependency>
            <groupId>ru.tinkoff.invest</groupId>
            <artifactId>openapi-java-sdk-java8</artifactId>
            <version>0.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.10.0-RC1</version>
        </dependency>
    </dependencies>

Для хранения параметров приложения добавляем класс Parameters. В нем будут храниться токен и признак запуска в режиме "песочница" (тестовый режим). Дабы не хранить токены в настройках, параметры будем передавать в качестве аргументов при запуске приложения.

Settings
package core;

import lombok.Data;

@Data
public class Parameters {

    private final static int ARGUMENTS_NUMBER = 2;
    private String token;
    private boolean sandBoxMode;

    public Parameters(String[] args) {
        if (args.length < 2)
            throw new IllegalArgumentException(String.format(
                    "Invalid number of arguments [%d], expected [%d]",
                    args.length, ARGUMENTS_NUMBER));
        setParameters(args[0], Boolean.parseBoolean(args[1]));
    }

    public Parameters(String token, boolean sandBoxMode) {
        setParameters(token, sandBoxMode);
    }

    private void setParameters(String token, boolean sandBoxMode) {
        this.token = token;
        this.sandBoxMode = sandBoxMode;
    }

    @Override
    public final String toString() {
        return String.format("core.Parameters: sandBoxMode = %s", sandBoxMode ? "true" : "false");
    }

}

Для работы с программным интерфейсом TINKOFF INVEST API необходимо создать подключение к нему. Эту функцию реализует класс ApiConnector, единственным его назначением является возвращение подключения (метод getOpenApi), данный класс реализует интерфейс AutoCloseable для закрытия подключения.

ApiConnector
package core;

import lombok.extern.slf4j.Slf4j;
import ru.tinkoff.invest.openapi.OpenApi;
import ru.tinkoff.invest.openapi.model.rest.SandboxRegisterRequest;
import ru.tinkoff.invest.openapi.okhttp.OkHttpOpenApi;

@Slf4j
public class ApiConnector implements AutoCloseable {

    private final Parameters parameters;
    private OpenApi openApi;

    public ApiConnector(Parameters parameters) {
        this.parameters = parameters;
    }

    public OpenApi getOpenApi() throws Exception {
        if (openApi == null) {
            close();
            log.info("Create TINKOFF INVEST API connection");
            openApi = new OkHttpOpenApi(parameters.getToken(), parameters.isSandBoxMode());
            if (openApi.isSandboxMode()) {
                openApi.getSandboxContext().performRegistration(new SandboxRegisterRequest()).join();
            }
        }
        return openApi;
    }

    @Override
    public void close() throws Exception {
        if (openApi != null) {
            openApi.close();
            log.info("TINKOFF INVEST API connection has been closed");
        }
    }
}

Получение инструментов

Сначала мы создаем инструменты, затем инструменты создают нас. (Герберт Маршалл Маклюэн – исследователь, литературный критик, филолог)

За получение информации из TINKOFF API отвечает класс ContextProvider.

Методы класса:

  • getStocks – возвращает список акций;

  • getBonds – возвращает список облишаций;

  • getEtfs – возвращает список биржевых фондов;

  • getCurrencies – возвращает список валют;

  • getOpenApi – возвращает программный интерфейс TINKOFF OPEN API.

Методы получения информации однотипны, все они, в свою очередь, запускают асинхронно методы получения данных TINKOFF INVEST API и возвращают объект класса MarketInstrumentList, из которого можно получить список инструментов (MarketInstrument).

ContextProvider
package core;

import lombok.extern.slf4j.Slf4j;
import ru.tinkoff.invest.openapi.OpenApi;
import ru.tinkoff.invest.openapi.model.rest.MarketInstrumentList;

@Slf4j
public class ContextProvider {

    private ApiConnector apiConnector;

    public ContextProvider(ApiConnector apiConnector) {
        this.apiConnector = apiConnector;
    }

    public MarketInstrumentList getStocks() throws Exception {
        return getOpenApi().getMarketContext().getMarketStocks().join();
    }

    public MarketInstrumentList getBonds() throws Exception {
        return getOpenApi().getMarketContext().getMarketBonds().join();
    }

    public MarketInstrumentList getEtfs() throws Exception {
        return getOpenApi().getMarketContext().getMarketEtfs().join();
    }

    public MarketInstrumentList getCurrencies() throws Exception {
        return getOpenApi().getMarketContext().getMarketCurrencies().join();
    }

    private OpenApi getOpenApi () throws Exception {
        return apiConnector.getOpenApi();
    }

}

MarketInstrument – класс из пакета ru.tinkoff.invest.openapi, его объектами могут быть различные финансовые инструменты – акции, фьючерсы, валюты, и т.п., и как все универсальное, имеет чрезмерную избыточность.

Свойства MarketInstrument:

  • ticker – краткое наименование инструмента (англ. Financial Instrument Global Identifier);

  • figi – глобальный идентификатор финансового инструмента (англ. Financial Instrument Global Identifier);

  • isin – Международный идентификационный код ценной бумаги (англ. International Securities Identification Number);

  • minPriceIncrement – минимальный шаг цены;

  • lot – минимальный лот;

  • minQuantity – минимально доступное к покупке количество;

  • currency – валюта;

  • name – наименование;

  • type – тип инструмента.

Типы инструментов (MarketInstrument.type):

  • currency – валюты;

  • etf – биржевые фонды;

  • bond – облигации;

  • stock – акции.

В чем же избыточность, о которой я говорил? Например, у инструмента целых 3 идентификатора (ticker, figi и isin), у валюты не может быть значения isin, а у облигаций значение полей ticker и isin идентичны, поле type нам никак не пригодится, т.к. мы всегда знаем инструмент какого типа нам возвращается.

Построение отчетов

Искусство простоты — это сложная головоломка. (Дуглас Хортон – американский священник и ученый)

Как показал предыдущий раздел, с инструментами не все так просто, и если вам так не показалось, то забегая вперед скажу, что всем нам давно и хорошо знакомый идентификатор ticker (тикер) не уникален! Сюрприз? По меньшей мере для меня осознание данного факта стало весьма неприятной неожиданностью, ибо в уже спроектированной базе данных посыпались ошибки при загрузке инструментов. Неприятно конечно, неприятно!

Первым функциональным решением на пути к разработке торговой платформы будет построение отчетов по существующим инструментам. Полученные результаты нам пригодятся для анализа и дальнейшего проектирования. Да и просто интересно знать перечень и характеристики финансовых инструментов, которые доступны у Тинькова. Говоря по правде, дальнейший код к разработке робота имеет опосредованное отношение, ваше время дорого, я это ценю, так что если спешите, то переходите сразу к выводам.

Для формирования отчетов я разработал класс CommonReport, он позволяет с помощью топора и такой-то матери дженериков и рефлексии строить отчеты по спискам произвольных классов. Идея в том, чтобы в соответствии с переданными настройками получить отчет с установленными столбцами, заголовком, кодировкой и разделителем.

CommonReport
package reports;

import lombok.extern.slf4j.Slf4j;
import ru.tinkoff.invest.openapi.model.rest.MarketInstrument;
import tools.IoTools;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;

@Slf4j
public class CommonReport<T> {

    private static final String DEFAULT_REPORT_DIRECTORY = "reports";
    private static final String DEFAULT_REPORT_CHARSET = "utf-8";
    private static final String DEFAULT_REPORT_DELIMITER = ";";

    protected String reportDirectory = DEFAULT_REPORT_DIRECTORY;
    protected String reportCharset = DEFAULT_REPORT_CHARSET;
    protected String reportDelimiter = DEFAULT_REPORT_DELIMITER;
    protected String fileName = String.format("%s.rpt", this.getClass().getSimpleName()).toLowerCase();
    protected String[] headers;
    protected String[] fields;
    protected String reportName = "";
    private List<T> reportObjects;

    private Path filePath = Paths.get(reportDirectory, fileName);

    public CommonReport() throws IOException {
        setReportDirectory(DEFAULT_REPORT_DIRECTORY);
    }

    public CommonReport setReportObjects(List<T> reportObjects) {
        this.reportObjects = reportObjects;
        return this;
    }

    public CommonReport setReportName(String reportName) {
        this.reportName = reportName;
        return this;
    }

    public CommonReport setReportDelimiter(String reportDelimiter) {
        this.reportDelimiter = reportDelimiter;
        return this;
    }

    public CommonReport setReportCharset(String reportCharset) {
        this.reportCharset = reportCharset;
        return this;
    }

    public CommonReport setHeaders(String[] headers) {
        this.headers = headers;
        return this;
    }

    public CommonReport setFields(String[] fields) {
        this.fields = fields;
        return this;
    }

    public CommonReport setFileName(String fileName) {
        this.fileName = fileName;
        setFilePath();
        return this;
    }

    public String getReportName() {
        return reportName;
    }

    public Path getFilePath() {
        return filePath;
    }

    public String[] getHeaders() {
        return headers;
    }

    public String[] getFields() {
        return fields;
    }

    public String getReportDelimiter() {
        return reportDelimiter;
    }

    public String getReportCharset() {
        return reportCharset;
    }

    public String getFileName() {
        return fileName;
    }

    public CommonReport setReportDirectory(String reportDirectory) throws IOException {
        this.reportDirectory = reportDirectory;
        IoTools.createDirectoryIfNotExists(Paths.get(reportDirectory));
        setFilePath();
        return this;
    }

    public String getReportDirectory() {
        return reportDirectory;
    }

    public String doExport() {
        int errCount = 0;
        String reportPath = null;
        log.info(String.format("Preparing of a report \"%s\" (%s)", getReportName(), getFileName()));
        try {
            initializeReport();
            Files.deleteIfExists(getFilePath());
            Files.write(getFilePath(), (getReportName() + "\n").getBytes(), StandardOpenOption.CREATE);
            Files.write(getFilePath(), (getReportHeader() + "\n").getBytes(), StandardOpenOption.APPEND);
            for (int i = 0; i < reportObjects.size(); i++) {
                try {
                    Files.write(
                            getFilePath(),
                            (getReportLine(reportObjects.get(i)) + "\n").getBytes(),
                            StandardOpenOption.APPEND);
                } catch (NoSuchFieldException | IllegalAccessException | IOException e) {
                    errCount++;
                    log.error(e.getMessage(), e);
                }
            }
            reportPath = getFilePath().toAbsolutePath().toString();
            if (errCount == 0)
                log.info(String.format("Report exported: %s", reportPath));
            else
                log.info(String.format(
                        "Report exported with %d error%s, see log for details: %s",
                        errCount,
                        errCount > 1 ? "s" : "",
                        reportPath));
        } catch (Exception e) {
            log.error(String.format("Report generation error: %s", e.getMessage()));
        }
        return reportPath;
    }

    protected void initializeReport() throws Exception {
        if (reportObjects == null)
            throw new VerifyError("No data to report");
        initializeFields();
    }

    private void setFilePath() {
        filePath = Paths.get(reportDirectory, this.fileName);
    }

    private String getReportLine(T object) throws NoSuchFieldException, IllegalAccessException {
        String line = "";
        for (int i = 0; i < fields.length; i++) {
            Field field = object.getClass().getDeclaredField(fields[i]);
            field.setAccessible(true);
            if (field.getType().toString().equalsIgnoreCase("class java.lang.String"))
                line = String.format("%s%s\"%s\"", line, reportDelimiter, field.get(object));
            else
                line = String.format("%s%s%s", line, reportDelimiter, field.get(object));
        }
        line = line.length() > 0 ? line.substring(1) : line;
        return line;
    }

    private String getReportHeader() {
        String header = "";
        for (int i = 0; i < headers.length; i++) {
            header = String.format("%s%s%s", header, reportDelimiter, headers[i]);
        }
        header = header.length() > 0 ? header.substring(1) : header;
        return header;
    }

    private void initializeFields() throws NoSuchFieldException {
        Field[] classFields = MarketInstrument.class.getDeclaredFields();
        if (fields == null || fields.length == 0) {
            fields = new String[classFields.length];
            for (int i = 0; i < classFields.length; i++) {
                fields[i] = classFields[i].getName();
            }
        }
        if (headers == null || fields.length != headers.length) {
            headers = new String[classFields.length];
            for (int i = 0; i < fields.length; i++) {
                headers[i] = classFields[i].getName();
            }
        }
        // check fields
        for (int i = 0; i < fields.length; i++) {
            MarketInstrument.class.getDeclaredField(fields[i]);
        }
    }

}

Для получения отчетов по валютам, биржевым фондам, акциям и облигациям созданы классы AllCurrenciesReport, AllEtfsReport, AllStocksReport и AllBondsReport, соответственно, все эти классы расширяют CommonReport и отличаются лишь настройками, поэтому приведу пример только класса AllCurrenciesReport, остальные можно посмотреть в репозитории проекта.

AllCurrenciesReport
package reports;

import ru.tinkoff.invest.openapi.model.rest.MarketInstrument;
import java.io.IOException;
import java.util.List;

public class AllCurrenciesReport extends CommonReport {

    public AllCurrenciesReport(List<MarketInstrument> instruments) throws IOException {
        super();
        this.setFileName("currencies.csv")
                .setReportObjects(instruments)
                .setReportName("Валюты")
                .setFields(new String[]{"ticker", "figi", "name", "currency", "lot", "minPriceIncrement"})
                .setHeaders(new String[]{"тикер", "figi", "наименование", "валюта", "лот", "шаг цены"});
    }

}

Для запуска отчетов создаем класс GetReports, в методе Main которого выполняем запрос данных из TINKOFF INVEST API и формирование отчетов. В качестве параметров в метод Main необходимо передать токен и режим запуска.

GetReports
import core.ApiConnector;
import core.ContextProvider;
import core.Parameters;
import reports.*;
import ru.tinkoff.invest.openapi.model.rest.MarketInstrument;

import java.util.ArrayList;
import java.util.List;

public class GetReports {

    public static void main(String[] args) throws Exception {
        Parameters parameters = new Parameters(args[0], Boolean.parseBoolean(args[1]));
        ApiConnector apiConnector = new ApiConnector(parameters);
        ContextProvider contextProvider = new ContextProvider(apiConnector);
        List<CommonReport> reports = new ArrayList<>();
        reports.add(new AllBondsReport(contextProvider.getBonds().getInstruments()));
        reports.add(new AllCurrenciesReport(contextProvider.getCurrencies().getInstruments()));
        reports.add(new AllStocksReport(contextProvider.getStocks().getInstruments()));
        reports.add(new AllEtfsReport(contextProvider.getEtfs().getInstruments()));
        reports.forEach(CommonReport<MarketInstrument>::doExport);
    }
}

После выполнения программы получаем отчеты по инструментам.

Выводы

Ни в коем случае нельзя спешить с выводами — ответ на задачу всегда находится в конце, а не в начале. (Аркадий и Георгий Вайнеры – советские писатели-детективщики)

Полученные данные я свел в электронную таблицу excel. Полагаю, многим читателям будет интересно поковыряться в информации, полученной с помощью TINKOFF INVEST API. Лично я сделал для себя следующие, полезные для дальнейшей разработки выводы (ниже). А какие у вас мысли? Пишите в комментариях, будет очень полезно.

  • Инструменты с незаданным шагом изменения цены

Имеются инструменты, в которых шаг изменения цены попросту не задан. Не знаю баг ли это или фича, но не думаю, что торговому алгоритму понравится, когда на вход в числе значимых торговых параметров придет значение null.

Акции

тикер

isin

figi

наименование

валюта

лот

шаг

AANold

US0025353006

BBG000D9V7T4

Aaron's Inc

USD

1

null

AIV_old

US03748R7540

TCS3748R7540

Apartment Investment & Management

USD

1

null

CCMP_old

US12709P1030

TCS2709P1030

Cabot Microelectronics

USD

1

null

CHKold

US1651671075

TCS651671075

Chesapeake Energy Corporation

USD

1

null

IAC__old

US44891N1090

BBG000BKQG80

IAC InterActiveCorp

USD

1

null

IAC_old

US44919P5089

TCS000BKQG80

IAC InterActiveCorp

USD

1

null

LRN_old

US48273U1025

TCS000QSXPZ9

K12 Inc

USD

1

null

LUMNold

US1567001060

TCS000BGLRN3

CenturyLink

USD

1

null

MTCH_old

US57665R1068

TCS00B6WH9G3

Match Group Inc

USD

1

null

NOV_old

US6370711011

TCS000BJX8C8

National Oilwell Varco

USD

1

null

POLold

US73179P1066

TCS000C8NJ10

PolyOne Corp

USD

1

null

RUAL_old

JE00B5BCW814

TCS008F2T3T2

РУСАЛ

RUB

10

null

SGEN_old

US8125781026

TCS000BH0FR6

Seattle Genetics Inc

USD

1

null

SLGold

US78440X1019

TCS000BVP5P2

SL Green Realty

USD

1

null

TE

NL0014559478

BBG00Z9D5GD9

Technip Energies N.V.

EUR

1

null

TOT

IZHBULDINKDK

IZHBULDINKDK

TotalEnergies SE

USD

1

null

Облигации

тикер

isin

figi

наименование

валюта

лот

шаг

ISSUANCEBRUS

ISSUANCEBRUS

ISSUANCEBRUS

Брусника 001P-01

RUB

1

null

ISSUANCESAMO

ISSUANCESAMO

ISSUANCESAMO

Самолет БО-П08

RUB

1

null

ISSUANCERESO

ISSUANCERESO

ISSUANCERESO

РЕСО-Лизинг БО-П выпуск 03

RUB

1

null

Да, я заметил, что большинство тикеров акций с некорректным шагом цены имеют окончание "old", которое неявно намекает на то, что пациент скорее мертв и присутствует лишь для хранения исторических данных (но это не точно), но есть среди них акции и без данного окончания. Полагаю, во избежание неприятных последствий, такие инструменты не стоит использовать в автоматической торговле. А вы что думаете?

  • Форматы хранения числовых данных

Минимальное значение шага цены для финансовых инструментов – 1.95E-7, а максимальное – 12844. Таким образом, для шага цены необходимо зарезервировать тип данных с 5 знаками слева от запятой и 9 знаками после. Мы ведь все понимаем, что для хранения дробных чисел нам нельзя использовать форматы с плавающей точкой?

  • Уникальный идентификатор

В качестве уникального идентификатора необходимо использовать figi, т.к. ticker уникален лишь в пределах биржи на которой обращается инструмент, а isin может быть не задан, например, для валют. Основываясь на данной информации, можно сделать выводы о том, что в качестве уникального идентификатора финансового инструмента разумно использовать figi (Financial Instrument Global Identifier).

Ссылки

Заключение

Никто из нас не умнее всех нас вместе. (Кен Бланшар – американский эксперт по менеджменту и автор книг)

Следующую статью я планирую посвятить теме получения исторических данных и online котировок, разработке схемы данных для их хранения, рассмотреть тему ограничений API в данной области, легальных способах обойти их, не нагружая лишний раз ресурсы брокера.

Остались вопросы, увидели ошибку, есть годная идея или же конструктивная критика, пишите, все обсудим. О критических замечаниях прошу уведомлять меня в личных сообщениях, буду стараться оперативно править. Коммьюнити – воистину великая сила. Следующую часть ждите примерно через 3 недели.

Комментарии (30)


  1. jvmdude
    11.12.2021 21:55
    +4

    Лучше бы вы начали с чтения багтрекера openapi. Столько фатальных проблем, висящих месяцами. Ещё и разработка полностью заглохла - последний релиз в начале 2020 года. И фьючерсами торговать нельзя (обещают в api v2, но когда оно будет неизвестно).

    Думаю, у вас всё застрянет на побаловался и бросил.

    У брокера Алор есть REST/WEBSOCKET API, с фьючерсами. Правда, судя по телеграм каналу, такое же глючное и падающее.

    Серьезный подход - это купить у ММВБ шлюз fix или plaza II. Небесплатно, но вы деньги свои туда понесёте.


    1. SkyZion Автор
      11.12.2021 22:16
      +1

      Я читал :). Особенно "порадовали" про API 2.0 и фьючерсы.

      Но тут вот в чем дело, я не рассматриваю торгового робота, как грааль, который поможет заработать стопицот миллионов, смысл данной задачи лишь в том, чтобы удовлетворить запрос читателей на просьбу сделать пример торгового робота. Для меня лично API - это источник данных о котировках и портфеле для анализа.

      Что касается проблем, висящих месяцами, как человек, разрабатывающий ПО для банков, могу сказать, что аналогичная ситуация характерна для любого, подчеркну, для абсолютно любого банка. И оперативно решаются лишь суперкритические задачи, которые не позволяют обслужить клиента, сдать корректную отчетность или закрыть операционный день. Очевидно, что проблемы пользователей API не относятся к таскам высокого приоритета. Я видел, задачи, которые висели годами, пока система не изменялась до такой степени, что баг не то что смоделировать невозможно, невозможно было понять о чем речь идет, так и закрывали.

      Что касается других API, могу сказать, что буду проектировать систему таким образом, чтобы разработанные интерфейсы позволяли подменить API на альтернативные, если потребуется. Ну а до plaza не дорос я еще... Идея в проекте :).

      Уверен, что подобные статьи побуждают интерес сотрудников TINKOFF к доработке продукта, ведь скорее всего, этот материал просмотрят несколько десятков тысяч человек, что не останется незамеченным в банке, как и наши с Вами комментарии.

      И напоследок, могу сказать, что разработчики TINKOFF – крутые, не смотря на то, что я пишу ПО для других банков, не могу не отметить того факта, что многие бизнес-процессы организованы очень классно! Что называется, снимаю шляпу перед коллегами по цеху. Будем надеяться, что и API доработают.


      1. jvmdude
        12.12.2021 09:17
        +3

        Ещё могу добавить, что сгенерить клиента сваггером, чем брать java SDK.

        У java SDK 2 года назад были лютейшие глюки вида: шлем ордер, прилетает ошибка 500, а ордер в реале отработан. И наоборот, шлем ордер, прилетает ОК, а ордер не отработан. Учитывая заброшенность всего этого хозяйства, баги могут быть не починенными.

        "разработчики TINKOFF – крутые"

        Хуже брокера не встречал для интрадея, даже если трейдить через оф приложение и веб терминал.


        1. SkyZion Автор
          12.12.2021 09:34

          А как Вам скорость ввода и вывода средств? Открытия или закрытия счетов? Работа службы поддержки, и т.п.? Стоит признать, что нет в Мире совершенства. Может я не настолько активно торгую, что не налетал на баги... Единственный баг, который я заметил - это отрисовка последней свечи на графике, я об этом писал в отзыве о клиенте, но получил стандартную отписку в виде бла, бла, бла, опишите проблему, заведите заявку на саппорте...

          В общем, радует одно - в конкуренции рождаются новые продукты и улучшаются существующие. Кстати, а какого брокера Вы порекомендуете? У меня основная претензия к ТИНЬКОФФ - это конские комиссии, в том числе за ведение счета...


          1. Ukaru
            13.12.2021 09:38
            +1

            Поддержка пока ниже плинтуса. Срок - год. Статус - премиум.


    1. DistortNeo
      11.12.2021 23:36
      +1

      Именно так. Вот скоро уже почти как 2 года исполнится багу:
      https://github.com/TinkoffCreditSystems/invest-openapi/issues/118
      С этим багом автоматическая торговая чуть более, чем бесполезна.

      Но как площадка, чтобы просто пощупать API работы с биржей и поиграться, Тинькофф идеален, потому что требует минимум финансовых затрат.


      1. SkyZion Автор
        11.12.2021 23:52

        Да уж ????


  1. Shepherd76
    11.12.2021 23:53
    +2

    два вопроса к аудитории:

    • тема статьи соответствует содержимому?

    • как вставить картинку "рука-лицо"?

    если серьёзно, то ув. автор! разработка торгового робота это: разработка торговой стратегии, проверка торговой стратегии на исторических данных и организации торговли в реалтайм

    а Ваша статья, судя по поставленным задачам, это лишь получение исторических данных по торговому инструменту и визуализация их.

    упомянутый Метатрейдер (всуе) - делает это из "коробки", плюс...даже огромный плюс - он умеет тестировать торговые стратегии и в "два клика" подключает любую .dll написанную на С++ или С#

    ну и конечное есть еще Python и большое комьюнити алготрейдинга, где можно загрузить исторические данные по торговому инструменту и создать торгового робота

    ну и наконец то сам вопрос - когда будет разрабатываться сам торговый робот, а не торговое окружение и визуализация? - примерные сроки, темой интересуюсь, но увы, только в части торговых роботов

    заранее спасибо


    1. SkyZion Автор
      12.12.2021 00:10
      +1

      Не все сразу, думаю, что в 3-й или 4-й части будет торговый алгоритм. С перерывом на отпуск и праздники, это ориентировочно февраль-март 2022 года, к счастью, есть ещё работа помимо хабоа, а написание статей требует времени, при этом никак не влияет на кошелек.

      Что касается MetaTrader, я пробовал с ним работать. Не понравилось, C-подобный прикладной язык лично мне не подошел, хотя, если кому-то нравится, то в чем проблема?

      По поводу торговой стратегии. Я не зацикливаюсь на конкретной, суть в том, чтобы можно было на вход подавать любой торговый алгоритм, т.е. для себя я ставлю задачу несколько шире, нежели реализация конкретного алгоритма.

      На счёт Python, равно как и других языков, тут и вовсе спорить не о чем. Вы ведь слышали о микросервисной архитектуре, понимаете в чем суть? Не важно на чем писать, главное, чтобы была возможность интеграции.

      Ну и раз уж Вы проявляете интерес к данной теме, давайте совместно разработаем и протестируем торговый алгоритм? Как насчёт этого?


      1. Shepherd76
        12.12.2021 00:33
        +4

        Ну и раз уж Вы проявляете интерес к данной теме, давайте совместно разработаем и протестируем торговый алгоритм? Как насчёт этого?

        протестировать алгоритм могу, МТ5/МТ4 знаю на 99% +1% багов терминалов, с Python не хватает времени закончить изучение, но все решаемо

        разрабатывать торговый алгоритм ... я пас - протестировано 100500 идей за 5 или 6 лет, единственное понимание, чтобы всегда торговать в профит - нужно знать будущее, а вот с этим как то пока не получается

        3 месяца не срок, понаблюдаю за циклом статей, может и я не прав, а у Вас все по полочкам - нужно только подождать :)


        1. SkyZion Автор
          12.12.2021 08:50
          +1

          Забавно! Я тоже прихожу к такому выводу ))). Именно по этой причине я не ставлю себе амбиций задачи в разработке Грааля по заработку. А вот хорошего советника вполне можно написать, тем более у меня есть в друзьях опытный инвестор, который в ЛЧИ занимает неплохие позиции. Попробую его привлечь.


  1. zversky
    12.12.2021 00:14

    Ну ведь не допотопными QUIK и MetaQuote с их конструкциями из костылей на LUA и MQL пользоваться в 21 веке?


    Не совсем понятно чем не угодил MQL. Понятно что урезанный язык. Вот только работает корректно, без этих диких сюрпризов что вы описали выше. Более того все эти ваши пляски с бубном в MQL решаеются одной строчкой:

    Print(SymbolInfoDouble("EURUSD",SYMBOL_BID));


    Следующую статью я планирую посвятить теме получения исторических данных и online котировок,


    Не мучайтесь, вашу следующую статьи можно сократить до:

    Print(iClose("EURUSD",PERIOD_D1,7));


    Прямо из коробки! Вот прям сел, написал, запустил. Все. Вот они которовки!
    Не нравится чистый MQL — теперь можно на Phyton.

    А что вы будете делать когда надо будет тестировать стратегию? Свой тестер писать?
    А график наложить — свое GUI?

    Я не спорю, что при профессиональной торговле это определенно может потребоваться. Только уже есть готовые решения, к чему свой велосипед? Вы только на тестирование кода потратите не один год: правильный расчет свопов, расчет кроскурсов, дивиденды, тиковые истории, кэши истории… да там столько всего.

    Ну ок, возможно у вас много времени и энергии. Но при указанных вами багах я бы даже не подходил к такому АПИ. Правильно вам выше сказали — курите FIX уважаемых брокеров.

    И я не топлю за MQL. Про Квик вообще не скажу. Претензия заключается в том, что вы охаяли нормальное и рабочее в целом решенее, предлагая в замен позаниматься сексом с вообще каким-то трэшем:

    Имеются инструменты, в которых шаг изменения цены попросту не задан.


    Не надо так. Люди смотрят.


    1. SkyZion Автор
      12.12.2021 00:32

      Справедливое замечание!

      Мне не понравился MQL. Прикладной язык обладает известными ограничениями в интеграции с внешними системами. В остальном, с вашими доводами сложно спорить.

      Касаемо тестеров стратегий, я планирую использовать библиотеку TA4J. Судя по описанию, необходимые инструменты в ней имеются, но на практике я с ней ещё не знаком. Посмотрим, попробуем.

      Для GUI, на JAVA полно библиотек для визуализации, например, JFREECHART о котором я писал.

      Не думаю, что я кого-то обидел, высказыв свое мнение про MQL. Попробовав писать на нем, я пришел к заключению, что не стоит ждать его развития. Возможно, я заблуждаюсь... Посмотрим.


      1. Shepherd76
        12.12.2021 00:51

        Мне не понравился MQL. Прикладной язык обладает известными ограничениями в интеграции с внешними системами. В остальном, с вашими доводами сложно спорить.

        Вы точно видели MQL ?

        не видел МТ3, но по утверждениям разработчиков даже в нем было реализован вызов внешних динамических библиотек .dll

        в МТ5 давно уже реализована интеграция с C# (правда только с .NET Framework) - все упрощено до "два клика", не нужно описывать даже сигнатуры функций подключаемой библиотеки на .NET Framework - все подключение пишется в одной команде:

        #import "имя_файла.dll"

        .... пока остановлюсь, обещал же пару месяцев понаблюдать


  1. zversky
    12.12.2021 00:47
    +2

    Прикладной язык обладает известными ограничениями в интеграции с внешними системами


    Это не так:

    //-------------------------------------------------
    #import  "Wininet.dll"
    int InternetOpenW(string, int, string, string, int);
    int InternetConnectW(int, string, int, string, string, int, int, int);
    bool InternetReadFile(int, uchar &buffer[], int, int& one_int[]);
    int InternetCloseHandle(int);
    int HttpOpenRequestW(int, string, string, string, string, char& AcceptTypes[], int, int);
    bool HttpSendRequestW(int, string, int, string, int);
    int InternetSetOptionW(int hInternet, int dwOption, int& lpBuffer[], int dwBufferLength);
    #import
    //-------------------------------------------------
    #import "kernel32.dll"
    int GetLastError(void);
    #import
    //-------------------------------------------------
    #import "foo.dll"
    int bar(void);
    #import


    … и весь мир у ваших ног


    1. SkyZion Автор
      12.12.2021 07:59

      Здорово! Если честно, я не погружался, настолько глубоко в MQL, возможно, мои выводы насчёт ограниченности несколько поспешны. Очевидно, в вопросах MQL у Вас больше опыта.

      Напомните, мне, как можно установить робота, разработанного на MQL на арендованный сервер, например Digital Ocean, и т.п.?


      1. Shepherd76
        12.12.2021 09:40
        +1

        Напомните, мне, как можно установить робота, разработанного на MQL на арендованный сервер, например Digital Ocean, и т.п.?

        Metatrader работает только под Windows, можно запустить под Wine - разработчики утверждают, что добились стабильной работы. А так все на "уровне домохозяйки" - установили терминал, положили робота в папку Expert - открыли чарт торгуемого инструмента и перетянули мышкой робота на чарт из навигатора терминала.

        Metatrader юзерфрендли софт, а негатив о нем по другую сторону терминала - не стационарность временных рядов, помноженная на большое количество жуликов - ДЦ


        1. SkyZion Автор
          12.12.2021 09:42

          Спасибо, именно к этому я и вел.


      1. zversky
        12.12.2021 12:14

        Напомните, мне, как можно установить робота, разработанного на MQL на арендованный сервер, например Digital Ocean, и т.п.?


        Создается впечатление, что вы не просто не интересовались MQL, а даже не заходили на их сайт, начав пилить свой велосипед. Но, не беспокойтесь — я сам такой же.

        Есть как минимум два варианта:
        — Арендовать любой VPS на Windows
        — Использовать встроенный VPS в MetaTrader: MetaTrader 5 Virtual Hosting: Run your trading robots and signal subscriptions 24/7 (Forex VPS)

        В последнем варианте вообще минимум телодвижений.
        Фразы ниже в комментариях типа: «Не стал учить MQL...» вообще честно говоря звучат странно. Чего там учить — весь MQL это набор из сотни — другой специальных функций с Cpp подобным синтаксисом. Если вы владеете Java — вы простого робота напишите за один вечер.

        Но я не об этом. Вы вот сейчас горите идеей своей стратегии которая принесет вам миллионы. Как и многие, многие и очень многие до вас) И пошли по длинному пути.
        Я вам ОЧЕНЬ рекомендую сделать черновик на любом доступном софте потратив как можно меньше времени, прогнать свою стратегию в тестере и убедиться, что она не работает ни черта и забыть об этом.
        Ну или двигаться дальше.
        Начинать ковырять АПИ без рабочей и проверенной страегии — время в пустоту.


        1. SkyZion Автор
          12.12.2021 12:20

          Да знаю я про VPS. Привязка к платформе, отсутствие возможности интеграции с внешними системами, да что я по 10 раз повторяю одно и то же?

          Речь ведь про Tinkoff invest API и Java, а не Meta Trader и MQL.


          1. zversky
            12.12.2021 12:43

            Если вы:

            Да знаю я про VPS

            То для чего вы спрашиваете:
            Напомните, мне, как можно установить робота, разработанного на MQL на арендованный сервер


            отсутствие возможности интеграции с внешними системами

            Вы ни разу так и не сказали в чем собственно вы испытываете ограничение. Пока, судя по комментариям, все ваши ограничения из-за узкого кругозора.


            1. SkyZion Автор
              12.12.2021 13:00

              Как написать интерфейс к данной системе? А подключить телеграмм-бота, а загрузить данные в СУБД? Или запустить вне windows? А может Вы нашли способ работать с нескольким десятком инструментов в своей стратегии? Вы не видите этих ограничений?

              И даже если Вы найдете как обойти часть этих ограничений, это не отменит того факта, что MQL - прикладной язык, который служит для определенных целей, и сравнивать его с Java не имеет никакого смысла.


              1. Shepherd76
                12.12.2021 14:05

                Python все умеет "из коробки"

                повторюсь еще раз - это все умеет MetaTrader 5 "из коробки" , MetaTrader 4 все умеет то же самое, кроме штатных встроенных в язык функций для работы с SQLite и тестирования стратегий на нескольких торговых инструментах - именно тестирования. Это все без подключения сторонних библиотек, все средствами языка MQL = С++ начала 2000-х годов.

                .... хотя так то да... без операционной системы Windows (Wine ?) - MetaTrader не имееют смысла

                ЗЫ: а без установленной Java Platform слабо запустить взаимодействие с Tinkoff Invest API ?

                ЗЫЗЫ: увлекаться начал, нужно останавливаться, у каждого есть свой путь с удачно разбросанными граблями. Не обращайте внимания!


              1. zversky
                12.12.2021 14:15
                +1

                Вы не видите этих ограничений?

                Да нет их.
                Вы, уважаемый, либо упорно не желаете пользоваться Гуглом, либо это потенциальное желание оставаться слепым:
                отсутствие возможности интеграции с внешними системами, да что я по 10 раз повторяю одно и то же?

                Вот и я удивляюсь. Перестаньте повторять и забейте каждый из ваших вопросов выше в соседнюю вкладку браузера, не сюда. Здесь за вас искать не будут. Вы удивитесь.
                MQL — прикладной язык, который служит для определенных целей

                Так ктож с этим спорит! До вас пытаются донести, что конкретно в вашем случае это НЕ недостаток, а специально оптимизированный инструмент под конкретно вашу задачу.

                То что вы пытаетесь сделать в вашей статье лишено смысла. Что делает и статью в целом безполезной.


                1. zversky
                  12.12.2021 14:18

                  Вы не улавливаете главного посыла:

                  Но я не об этом. Вы вот сейчас горите идеей своей стратегии которая принесет вам миллионы. Как и многие, многие и очень многие до вас) И пошли по длинному пути.
                  Я вам ОЧЕНЬ рекомендую сделать черновик на любом доступном софте потратив как можно меньше времени, прогнать свою стратегию в тестере и убедиться, что она не работает ни черта и забыть об этом.


                  Хотя ешьте кактусы…


  1. TradeSpeculator
    12.12.2021 10:16
    +1

    Поддержу автора в желании написать софт на языке, запрос по названию которого (java) выдаст огромный список вакансий на любом сайте поиска специалистов/работы. (Запрос программист java).

    Сам как программист 1с в тоже не стал учить ни MQL ни LUA. балуюсь этой же темой на 1С+Python


    1. SkyZion Автор
      12.12.2021 10:30

      Я пробовал писать на MQL, сначал был в полном восторге, затем пришел к выводу, что существующие ограничения мне не очень нравятся. Так и пришел к Тинькофф API. Хотя, судя по комментариям, очень многие успешно пользуются MQL и, возможно, я был несправедливо резок в своем высказывании относительно MQL.


      1. TradeSpeculator
        12.12.2021 13:29
        +1

        Никого не слушайте и продолжайте идти своим путем. Понятно, что конкуренты будут писать плохо, приводить свои доводы, понятные только им. Просто сделайте свою систему. На этом рынке нет никаких правил и лидеров рынка.


  1. NeverIn
    13.12.2021 09:37
    +1

    Вообще не понимаю о чем тут спор идет ?! Автор предложил обучающий пример на базе Java? зачем тут вообще ссылаться на готовые профессиональные проекты типа MetaTrader ?!


    1. SkyZion Автор
      13.12.2021 09:40

      Так бывает, когда комьюнити MT больше, чем java в данной конкретной области.