TINKOFF-INVEST. Разработка торгового робота на JAVA. Часть 1

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
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 упорно не замечают, что на их ресурсах размещена ссылка на нерабочий ресурс, тревожный звоночек...

  • Общая информация

  • Документация

  • GitHub 

  • JAVA SDK

  • Telegram канал 

  • Telegram чат (чат ничей, живите кто хотите)

Получаем токены 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).

Ссылки

  • Репозиторий GitHub

  • Анализ инструментов

Заключение

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

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

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

Источник: https://habr.com/ru/post/592093/


Интересные статьи

Интересные статьи

Привет, Хабр!На связи Паша Емельянов, тимлид в AGIMA. В этой статье расскажу, как на одном из проектов мы переписывали старый функционал, разработанный когда-то на PHP, на Golang, с какими проблемами ...
Всем привет! В нашей прошлой статье мы рассказали об устройстве FortiSwitch и его основных функциональных возможностях. Вторая часть решения от компании Fortinet для обес...
Предлагаю ознакомиться с ранее размещенными материалами по проекту Starlink (SL): Часть 1. Рождение проекта ‣ Часть 2. Сеть SL ‣ Часть 3. Наземный комплекс ‣ Часть 4. Абонентский те...
Привет, меня зовут Стас, я работаю в команде Тинькофф Бизнеса. В прошлой статье мой коллега Ваня рассказал, как у нас устроена архитектура приложений. Несколько раз Ваня упомянул неки...
Основатель Instagram: — «Люди всегда были визуалами — наш мозг лучше обрабатывает именно изображения. Письмо не естественно для человека. В Instagram мы возвращаемся к тому, что наиболее естестве...