
Введение
Количество физических лиц, имеющих брокерские счета на Московской бирже, за июль 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 упорно не замечают, что на их ресурсах размещена ссылка на нерабочий ресурс, тревожный звоночек...
Telegram чат(чат ничей, живите кто хотите)
Получаем токены TINKOFF-INVEST
Получение токена
Зайдите в свой аккаунт на https://tinkoff.ru/
Перейдите в раздел инвестиций
Перейдите в настройки
Функция "Подтверждение сделок кодом" должна быть отключена
Выпустите токен OpenAPI для биржи и Sandbox. Возможно, система попросит вас авторизоваться еще раз. Не беспокойтесь, это необходимо для подключения робота к торговой платформе.
Скопируйте токен и сохраните его. Токен отображается только один раз, просмотреть его позже не получится. Тем не менее вы можете выпускать неограниченное количество токенов.
Создание подключения
Путь в тысячу ли начинается с первого шага. (Лао-цзы – древнекитайский философ 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 недели.
UPD: TINKOFF-INVEST. Разработка торгового робота на JAVA. Часть 2
