Блог им. AlekseyManin |Нейросеть и точка минимальных выплат.

Сделал нейросеть для прогноза цены фьючерса RTS к клирингу на основе точки минимальных выплат по опционам.

Как трактовать результаты — вообще не понимаю. Вроде что-то интересное и есть, если поставить количество эпох более 5000.  Ну это по коэффициенту детерминации еще более менее. А по loss и mae, вообще беда.

Кто занимается нейронками, гляньте пожалуйста. Может поставить входной слой LSTM и не перемешивать train, тогда результат будет лучше?

Ссылка на Colab notebook:

colab.research.google.com/drive/1MXjYUVcLMbiiE5hPauNtp9o8VE3QSu8c?usp=sharing


Блог им. AlekseyManin |Python. Как получить дату экспирации?

Пишу запросы на MOEX ISS, а там исторические данные(с прошедшей экспирацией) можно получить только с короткими названиями тикеров фьючерсов и опционов. Может кто-то уже писал код, чтобы вытащить дату экспирации из короткого названия тикера? Поделитесь пожалуйста.

Это один из запросов на MOEX:

<code>import requests

import apimoex
import pandas as pd

# request_url = ('http://iss.moex.com/iss/history/engines/futures/markets/options/securities.json?date=2021-12-01&assetcode=RIZ1')
request_url = ('http://iss.moex.com/iss/history/engines/futures/markets/options/securities.json?date=2021-12-01&assetcode=RTS')
arguments = {'securities.columns': (["BOARDID, TRADEDATE, SECID, OPEN, LOW, HIGH, CLOSE, OPENPOSITIONVALUE, VALUE, VOLUME, OPENPOSITION, SETTLEPRICE"])}

with requests.Session() as session:
    iss = apimoex.ISSClient(session, request_url, arguments)
    data = iss.get()
    df = pd.DataFrame(data['history'])
    df.set_index('SECID', inplace=True)
    print(df.to_string(max_rows=10, max_cols=15), '\n')
    df.info()</code>

Блог им. AlekseyManin |Точка минимальных выплат. Скрипт.

Написал скрипт на Python, который строит график с точкой минимальных выплат по выбранному фьючерсу. Скрипт основан на библиотеке QuikPy.

pastebin.com/FNNxSs52

Error: [31m The Parser function of type «linkTool» is not defined. Define your custom parser functions as: [34mhttps://github.com/pavittarx/editorjs-html#extend-for-custom-blocks [0m

Сам я не программист, поэтому на код не ругайтесь )))

Результат выдает в виде графика. Ждать нужно долго ))) Зато потом автообновляется каждые 20 секунд.

Точка минимальных выплат по опционам

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


Блог им. AlekseyManin |Синтетическая облигация. Помогите с Qlua.

Приветствую всех!

Сам я не программист, но решил написать скрипт, который будет выводить табличку по доходности синтетических облигаций (покупка акций/продажа фьючерса). Идея в получении дохода от контанго. Скрипт работает и табличка выводится, но через некоторое время появляется ошибка о недостатке памяти.

Подскажите, что я сделал не так?

Синтетическая облигация. Ошибка памяти.
<code>-- ©2022 by Aleksey Manin
-- Таблица расчета доходности синтетической облигации
-- Какие инструменты(тикеры) отслеживаем. Таблица пар тикер - площадка
tickers = {GAZP = {GAZP = "TQBR", GZZ2 = "SPBFUT"},
           SBER = {SBER = "TQBR", SRZ2 = "SPBFUT"},
           PLZL = {PLZL = "TQBR", PZZ2 = "SPBFUT"},
           GMKN = {GMKN = "TQBR", GKZ2 = "SPBFUT"},
           LKOH = {LKOH = "TQBR", LKZ2 = "SPBFUT"},
           AFLT = {AFLT = "TQBR", AFZ2 = "SPBFUT"},
           NVTK = {NVTK = "TQBR", NKZ2 = "SPBFUT"},
           YNDX = {YNDX = "TQBR", YNZ2 = "SPBFUT"},
           --MOEX = {MOEX = "TQBR", MXZ2 = "SPBFUT"},
           ALRS = {ALRS = "TQBR", ALZ2 = "SPBFUT"},
           VTBR = {VTBR = "TQBR", VBZ2 = "SPBFUT"},
           SNGS = {SNGS = "TQBR", SNZ2 = "SPBFUT"},
           MGNT = {MGNT = "TQBR", MNZ2 = "SPBFUT"},
           NLMK = {NLMK = "TQBR", NMZ2 = "SPBFUT"},
           MTSS = {MTSS = "TQBR", MTZ2 = "SPBFUT"},
           ROSN = {ROSN = "TQBR", RNZ2 = "SPBFUT"}}
rows = {} -- Список строк в таблице по количеству тикеров
oblig_t = AllocTable() -- Указатель на саму таблицу
stopped = false -- Остановка скрипта

-- Функция вызывается перед вызовом main
function OnInit(path)
  AddColumn(oblig_t, 0, "Ticker_BA", true, QTABLE_STRING_TYPE, 8) -- "Ticker"- название первого столбца в таблице
  AddColumn(oblig_t, 1, "Lot_BA", true, QTABLE_INT_TYPE, 8) -- 
  AddColumn(oblig_t, 2, "Ask_BA", true, QTABLE_DOUBLE_TYPE, 10) -- 
  AddColumn(oblig_t, 3, "Ticker_F", true, QTABLE_STRING_TYPE, 10) -- 
  AddColumn(oblig_t, 4, "Lot_F", true, QTABLE_INT_TYPE, 8) -- 
  AddColumn(oblig_t, 5, "Bid_F", true, QTABLE_DOUBLE_TYPE, 10) -- 
  AddColumn(oblig_t, 6, "Day_EXP", true, QTABLE_INT_TYPE, 10) -- 
  AddColumn(oblig_t, 7, "Date_EXP", true, QTABLE_DATE_TYPE, 15) -- 
  AddColumn(oblig_t, 8, "Dohod%", true, QTABLE_DOUBLE_TYPE, 10) -- 
  AddColumn(oblig_t, 9, "Dohod", true, QTABLE_DOUBLE_TYPE, 10) -- 

  CreateWindow(oblig_t) -- Создание окна таблицы
  SetWindowCaption(oblig_t, "Синтетическая облигация") -- Даем название таблице

  for ticker, two in pairs(tickers) do -- Перебираем пары БА-Фьючерс
    rows[ticker] = InsertRow(oblig_t, -1) -- Заносим тикер в список строк
  end
end

function Run()
  for ticker, two in pairs(tickers) do -- Перебираем пары БА-Фьючерс
    ask_ba = 0.0
    bid_f = 0.0
    lot_f = 0
    for ticker_two, board in pairs(two) do -- Перебираем Тикеры внутри пары БА-Фьючерс
      if ticker == ticker_two then -- Если Тикер БА
        SetCell(oblig_t, rows[ticker], 0, ticker_two) -- Заполняем ячейке Тикера БА
        SetCell(oblig_t, rows[ticker], 1, -- Заполняем лот БА
                string.format("%u", getParamEx (board, ticker_two, "LOTSIZE").param_value))
        ask_ba = getParamEx (board, ticker_two, "OFFER").param_value
        SetCell(oblig_t, rows[ticker], 2, string.format("%.2f", ask_ba)) -- Аск БА
      else -- Если Тикер фьючерса
        SetCell(oblig_t, rows[ticker], 3, ticker_two) -- Заполняем ячейку Тикера фьючерса
        lot_f = getParamEx (board, ticker_two, "LOTSIZE").param_value
        SetCell(oblig_t, rows[ticker], 4, string.format("%u", lot_f))
        bid_f = getParamEx (board, ticker_two, "BID").param_value
        SetCell(oblig_t, rows[ticker], 5, string.format("%u", bid_f))
        day_exp = getParamEx (board, ticker_two, "DAYS_TO_MAT_DATE").param_value
        SetCell(oblig_t, rows[ticker], 6, string.format("%u", day_exp))
        SetCell(oblig_t, rows[ticker], 7, 
                string.format("%u", getParamEx (board, ticker_two, "MAT_DATE").param_value))
        --message('Дата:'..getParamEx (board, ticker_two, "MAT_DATE").param_type)
      end
    end
    sum_ba = ask_ba * lot_f
    --message('Тикер:'..ticker..' lot_f:'..lot_f..' sum_ba:'..sum_ba)
    sum_year = (bid_f - sum_ba) / day_exp * 365
    percent = sum_year * 100 / sum_ba
    SetCell(oblig_t, rows[ticker], 8, string.format("%.2f", percent))
    SetCell(oblig_t, rows[ticker], 9, string.format("%.2f", bid_f - sum_ba))
  end
end

-- Функция вызывается перед остановкой скрипта
function OnStop(signal) stopped = true end

-- Функция вызывается перед закрытием квика
function OnClose() stopped = true end;

-- Основная функция выполнения скрипта
function main()
  while not stopped do 
    Run()
    sleep(10) 
  end
end</code>
  • обсудить на форуме:
  • Quik Lua

Блог им. AlekseyManin |Доходность WIMM в сравнении с инфляцией.

Написал скрипт на Python, чтобы рассчитать доходность ETF WIMM (бывший VTBM).

pastebin.com/KnVx0Esp

Доходность WIMM в сравнении с инфляцией.

Сравнил доходность ETF WIMM с официальной инфляцией.

На картинке голубые цифры — это доходность WIMM. К сожалению доходность отстает от инфляции за редким исключением. 

Доходность WIMM в сравнении с инфляцией.

( Читать дальше )

Блог им. AlekseyManin |Range график из тиковых данных

Написал скрипт, который переделывает тиковые данные в range заданной размерности.

Но есть нюанс, когда идет быстрый рынок, некоторые бары могут иметь одинаковое время открытия, что приводит к некоторому несоответствию range баров.

Range график из тиковых данных
<code>"""
Скрипт из файлов с тиковыми данными делает файл с рандже барами
"""
import re
from datetime import datetime
from pathlib import *

import pandas as pd


def zero_hour(cell):
    """ Функция преобразует время (с финама приходят часы без нулей (с марта 2021), которые pandas не воспринимает)"""
    cell = f'{int(cell)}'
    tmp_time = datetime.strptime(cell, "%H%M%S")
    return tmp_time.strftime("%H%M%S")


def run(tick_files: list[Path], razmer: int, target_dir: Path):

    for ind_file, tick_file in enumerate(tick_files, start=1):  # Итерация по тиковым файлам

        list_split = re.split('_', tick_file.name, maxsplit=0)  # Разделение имени файла по '_'
        tiker = list_split[0]  # Получение тикера из имени файла
        date_quote_file = re.findall(r'\d+', str(tick_file))  # Получение цифр из пути к файлу
        target_name = f'{tiker}_range{razmer}_{date_quote_file[0]}.txt'  # Создание имени новому файлу
        target_file_range: Path = Path(target_dir / target_name)  # Составление пути к новому файлу

        if Path.is_file(target_file_range):
            print(f'Файл уже существует {target_file_range}')
            continue
        else:
            df_ticks_file: pd = pd.read_csv(tick_file, delimiter=',')  # Считываем тиковые данные в DF

            # Создание DF под рандже бары одного тикового файла
            df: pd = pd.DataFrame(columns='<DATE> <TIME> <OPEN> <HIGH> <LOW> <CLOSE> <VOL>'.split(' '))

            for tick in df_ticks_file.itertuples():  # Итерация по строкам тикового DF
                print('\rCompleted file: {:.2f}%. Completed files: {:.2f}%'.format(
                    tick[0] * 100 / len(df_ticks_file.index),
                    ind_file * 100 / len(tick_files)
                    ),
                    end=''
                )

                if tick[0] == 0:
                    # Добавление строки в DF с рандже барами
                    df.loc[len(df.index)] = [int(tick[1]), int(tick[2]), tick[3], tick[3], tick[3], tick[3], tick[4]]
                    continue

                # Если бар сформирован по размеру возрастающий бар
                if df.loc[len(df.index) - 1, '<LOW>'] + razmer < tick[3]:
                    df.loc[len(df.index) - 1, '<CLOSE>'] = df.loc[len(df.index) - 1, '<LOW>'] + razmer
                    df.loc[len(df.index) - 1, '<HIGH>'] = df.loc[len(df.index) - 1, '<CLOSE>']
                    # Добавление строки в DF с дельта барами
                    df.loc[len(df.index)] = [int(tick[1]), int(tick[2]), tick[3], tick[3], tick[3], tick[3], tick[4]]
                    continue
                    # break

                # Если бар сформирован по размеру падающий бар
                if df.loc[len(df) - 1, '<HIGH>'] - razmer > tick[3]:
                    df.loc[len(df) - 1, '<CLOSE>'] = df.loc[len(df) - 1, '<HIGH>'] - razmer
                    df.loc[len(df) - 1, '<LOW>'] = df.loc[len(df) - 1, '<CLOSE>']
                    # Добавление строки в DF с дельта барами
                    df.loc[len(df.index)] = [int(tick[1]), int(tick[2]), tick[3], tick[3], tick[3], tick[3], tick[4]]
                    continue
                    # break

                # Заполняем(изменяем) последнюю строку DF с рандже баром --------------------------------------
                # Записываем <CLOSE> --------------------------------------------------------------------------
                df.loc[len(df.index) - 1, '<CLOSE>'] = tick[3]  # Записываем последнюю цену как цену close бара

                # Записываем <HIGH> ---------------------------------------------------------------------------
                if float(tick[3]) > df.loc[len(df) - 1, '<HIGH>']:  # Если цена последнего тика больше чем high
                    df.loc[len(df.index) - 1, '<HIGH>'] = tick[3]  # Записываем цену последнего тика как high

                # Записываем <LOW> ----------------------------------------------------------------------------
                if float(tick[3]) < df.loc[len(df.index) - 1, '<LOW>']:
                    df.loc[len(df.index) - 1, '<LOW>'] = tick[3]  # Записываем цену последней сделки как low

                # Записываем <VOL> ----------------------------------------------------------------------------
                df.loc[len(df.index) - 1, '<VOL>'] += tick[4]  # Увеличиваем объем

            # Изменение типа колонок
            df[['<DATE>', '<TIME>', '<VOL>']] = df[['<DATE>', '<TIME>', '<VOL>']].astype(int)
            # Преобразуем столбец <TIME>, где нужно добавив 0 перед часом
            df['<TIME>'] = df.apply(lambda x: zero_hour(x['<TIME>']), axis=1)

            df.to_csv(target_file_range, index=False)  # Запись в файл для одного тикового файла

        # break


if __name__ == "__main__":
    razmer: int = 250
    ticker: str = 'RTS'
    year_tick: str = '2022'

    source_dir_tick: Path = Path(f'c:/data_quote/data_finam_{ticker}_tick')  # Путь к ресурсному каталогу
    target_dir: Path = Path(f'c:/data_quote/data_prepare_{ticker}_range')  # Путь к целевому каталогу

    # Создание списка путей к файлам с тиками
    tick_files: list[Path] = list(source_dir_tick.glob(f'*{year_tick}*.csv'))

    run(tick_files, razmer, target_dir)
</code>

Блог им. AlekseyManin |Купил и держи. Сравнение.

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

Интерпретацию результатов описал в предыдущей публикации. 

Особенность получающегося графика — это коряво выглядит ось Х(годы начала инвестирования), пришлось отказаться от строкового формата, ради смещения новых столбиков.

SPY, BRK-B, IJH, QQQ AAPL, BRK-B, IJH, QQQ

( Читать дальше )

Блог им. AlekseyManin |Buy and Hold. Купил и держи.

Решил в Python протестировать стратегию «Купил и Держи». Причем захотелось посмотреть какой будет доход если инвестировать ежемесячно равные суммы в течении определенного периода. 

Немного об интерпретации результатов:

1. Дивиденды не учитываются. Учитывается только курсовой рост.

2. Доход по стратегии показан напротив года начала инвестирования, хотя фактически он соответствует дате начала инвестирования + заданный период.

3. На картинках QQQ, $100 ежемесячно в течении 10 лет

Это цифры )Это график )

( Читать дальше )

Блог им. AlekseyManin |Данные из QUIK в Python. Построение Дельта графика.

Данные из QUIK в Python. Построение Дельта графика.Построение нестандартных графиков в Python при помощи библиотеки finplot.
Можно строить почти любые нестандартные графики: Range, Renco, Delta.
В качестве примера скрипт для построения Дельта графика.
График строиться с момента запуска по поступающим данным из таблицы обезличенных сделок.
Для получения данных из КВИКа используется PythonServer Евгения Шибаева (огромное спасибо автору!!!)

Тапками не кидайтесь, программировать только учусь.

# В КВИКе запускаем луа-скрипт QuikLuaPython.lua
import socket
import threading
from datetime import datetime, timezone
import pandas as pd
import finplot as fplt

fplt.display_timezone = timezone.utc


class DeltaBar():
    def __init__(self):
        self.df = pd.DataFrame(columns='date_time open high low close delta delta_time_sec'.split(' '))
        self.df.loc[len(self.df)] = [0, 0, 0, 0, 0, 0, 0]

    def parser(self, parse):
        if parse[0] == '1' and parse[1] == 'RIH1':
            if abs(self.df.iloc[len(self.df) - 1]['delta']) >= 500:
                self.df.loc[len(self.df)] = [0, 0, 0, 0, 0, 0, 0]  # Добавляем строку в DF

            self.df.iloc[len(self.df) - 1]['close'] = float(parse[4])  # Записываем последнюю цену как цену close бара

            if self.df.iloc[len(self.df) - 1]['date_time'] == 0:
                self.df.iloc[len(self.df) - 1]['date_time'] = \
                    datetime.strptime(f'{parse[7]} {parse[8][0:-1]}', "%d.%m.%Y %H:%M:%S.%f").replace(microsecond=0)

            if self.df.iloc[len(self.df) - 1]['open'] == 0:
                self.df.iloc[len(self.df) - 1]['open'] = float(parse[4])

            if float(parse[4]) > self.df.iloc[len(self.df) - 1]['high']:
                self.df.iloc[len(self.df) - 1]['high'] = float(parse[4])

            if (float(parse[4]) < self.df.iloc[len(self.df) - 1]['low']) or \
                    (self.df.iloc[len(self.df) - 1]['low'] == 0):
                self.df.iloc[len(self.df) - 1]['low'] = float(parse[4])

            if parse[5] == '1026':
                self.df.iloc[len(self.df) - 1]['delta'] += float(parse[6])

            if parse[5] == '1025':
                self.df.iloc[len(self.df) - 1]['delta'] -= float(parse[6])

            self.df.iloc[len(self.df) - 1]['delta_time_sec'] = \
                datetime.strptime(f'{parse[7]} {parse[8][0:-1]}', "%d.%m.%Y %H:%M:%S.%f") - \
                self.df.iloc[len(self.df) - 1]['date_time']
            self.df.iloc[len(self.df) - 1]['delta_time_sec'] = self.df.iloc[len(self.df) - 1]['delta_time_sec'].seconds


def service():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('127.0.0.1', 3587))  # Хост-этот компьютер, порт - 3587
    while True:
        res = sock.recv(2048).decode('utf-8')
        if res == '<qstp>\n':  # строка приходит от клиента при остановке луа-скрипта в КВИКе
            break
        else:
            delta_bar.parser(res.split(' '))  # Здесь вызываете свой парсер. Для примера функция: parser (parse)
    sock.close()


def update():

    df = delta_bar.df
    # Меняем индекс и делаем его типом datetime
    df = df.set_index(pd.to_datetime(df['date_time'], format='%Y-%m-%d %H:%M:%S'))
    # print(delta_bar.df)

    # pick columns for our three data sources: candlesticks and TD
    candlesticks = df['open close high low'.split()]
    volumes = df['open close delta_time_sec'.split()]
    if not plots:
        # first time we create the plots
        global ax
        plots.append(fplt.candlestick_ochl(candlesticks))
        plots.append(fplt.volume_ocv(volumes, ax=ax.overlay()))
    else:
        # every time after we just update the data sources on each plot
        plots[0].update_data(candlesticks)
        plots[1].update_data(volumes)


if __name__ == '__main__':
    delta_bar = DeltaBar()
    # Запускаем сервер в своем потоке
    t = threading.Thread(name='service', target=service)
    t.start()

    plots = []
    ax = fplt.create_plot('RIH1', init_zoom_periods=100, maximize=False)
    update()
    fplt.timer_callback(update, 2.0)  # update (using synchronous rest call) every N seconds

    fplt.show()
  • обсудить на форуме:
  • QUIK

....все тэги
UPDONW
Новый дизайн