3Qu
3Qu личный блог
12 мая 2020, 10:29

Моделирование Торговых Систем на Python. 2.

Тем, кто не читал предыдущий топик этой темы, рекомендую для начала ознакомиться с ним [1].

В комментариях к предыдущему топику меня критиковали за неоптимальность кода Python. Однако, текст читают люди с совершенно разной подготовкой — от почти не знающих Python или знающих другие языки программирования, до продвинутых пользователей. Последние легко могут обнаружить неоптимальность кода и заменить его своим. Тем не менее, код должен быть доступен и новичкам, возможно не обладающим знанием пакетов и продвинутых методов. Поэтому, в коде я буду, по возможности, использовать только базовые конструкции Python, не требующие глубоких знаний, и которые могут легко читаться людьми, программирующими на других языках. Вместе с тем, по мере изложения, без фанатизма, буду вводить и новые элементы Python.
Если вы хотите как-то улучшить или оптимизировать код, приводите его в комментариях — это только расширит и улучшит изложенный материал.

Ну, а сейчас мы займемся разработкой и тестированием индикаторов. Для начала нам нужна простейшая стратегия с использованием МА — его и построим. Самой лучшей по характеристикам МА является ЕМА. Формула ЕМА:

    Y(i) = a0*X(i) + b1*Y(i-1), где a0 + b1 = 1.

Однако коэффициенты ЕМА мы будем считать по другому:

    a0 = 1/(1+T/(2*pi) и b1 = T/(2*pi)/(1+T/(2*pi)), где pi = 3.14...,

что превращает ЕМА в полноценный Фильтр Нижних Частот (ФНЧ) 1-го порядка, в котором Т соответстует частоте отсечки фильтра fо = 1/T. В отличие от ЕМА, здесь параметр Т приобретает явный физический смысл, сам фильтр имеет прогнозируемые свойства, и с таким фильтром легко работать. Ну, коли он теперь фильтр, и назовем индикатор по другому cF1Bat().

Для проектирования фильтра в Python мы будем использовать классы (class). Для тех, кто не знаком с Объектно Ориентированным Программированием (ООП), class — это изолированный кусок кода, содержащий (инкапсулирующий) в себе данные и функции (методы). Объявляя или инициализируя class, мы создаем объект. Собственно, объявляя массив, структуру, строку символов или даже переменную, мы тоже создаем объекты под которые выделяется память. Все примерно тоже самое, ничего необычного.)
Ну, и сам код индикаторов:

# -*- coding: utf-8 -*-
"""
Created on Mon May 12 10:07:14 2020

@author: 3Qu
"""

import math


# Фильтр LPF 1-го порядка
class cF1Bat():

    def __init__(self, per, x):
        self.T = per
        # self.out = []
        self.Filter(x)

    def Filter(self, x):
        a0 = 1/(1+self.T/(2*math.pi))
        b1 = self.T/(2*math.pi)/(1+self.T/(2*math.pi))
        self.out = []
        for i in range(0, len(x)):
            if i == 0:
                self.out.append(x[i])
            else:
                self.out.append(a0*x[i]+b1*self.out[i-1])

    # 1-я производная
    def d1out(self, i):
        if i == 0:
            return 0
        return self.out[i]-self.out[i-1]

    # 2-я производная
    def d2out(self, i):
        if i == 0:
            return 0
        elif i == 1:
            return self.out[i]-self.out[i-1]
        return self.out[i]-2*self.out[i-1]+self.out[i-2]


# Фильтр среднее за период (подходит для калибровки) T - целое
class cFMean():

    def __init__(self, per, x):
        self.T = per
        # self.out = []
        self.Filter(x)

    def Filter(self, x):
        self.out = []
        for i in range(0, len(x)):
            if i == 0:
                self.out.append(x[i])
            elif i > 0 and i < self.T:
                self.out.append(self.out[i-1]+x[i]/self.T-x[0]/self.T)
            else:
                self.out.append(self.out[i-1]+x[i]/self.T-x[i-self.T]/self.T)

Кроме F1Bat(), там еще дополнительно введено простое скользящее среднее cFMean() — иногда бывает нужно.
Теперь скопируем текст в в файл Filters.py. В папке SmartLab (см. пред-й топик [1]) создадим папку SLPack, и поместим в нее наш файл Filters.py. Индикаторы готовы к использованию.

Теперь остается проверить работоспособность кода и калибровки индикаторов. Для этого напишем небольшой код и сохраним его в файле Calibr.py в папку SmartLab.

# -*- coding: utf-8 -*-
"""
Created on Mon May 12 10:09:55 2020

@author: 3Qu
"""

import matplotlib.pyplot as plt
import SLPack.Filters as flt  # загрузка нашего пакета с фильтрами

# создаем тестовую последовательность 1(t) - единичный скачок
x = [1 for i in range(0, 50)]
x[0] = 0

# создаем и инициализируем индикаторы
FM40 = flt.cFMean(40, x)
F40 = flt.cF1Bat(40, x)

# отображаем индикаторы на графике
plt.plot(FM40.out, label = 'FM40')  # добавили ярлык на график
plt.plot(F40.out, label = 'F40')
plt.title('Реакция на 1(t)')  # заголовок
plt.legend()  # отображаем ярлыки и легенду (если есть)
plt.grid()
plt.show()

Запускаем файл Calibr.py на исполнение и получаем картинку:
Моделирование Торговых Систем на Python. 2.

Как видим, все работает и идеально откалибровано, что и следовало ожидать от правильно построенного фильтра.
Думаю, теперь вы можете самостоятельно построить наши индикаторы на графике случайного блуждания [1], вывести на график заголовок, ярлыки и поэкспериментировать со всем этим.
Удачи!


Ссылки.
1. Моделирование Торговых Систем на Python. 1.

62 Комментария
  • Vadim Ch
    12 мая 2020, 10:57
    Отлично!
  • day0markets.ru
    12 мая 2020, 11:25

    Правильно критиковали. На питоне легко писать, как хороший код так и плохой.

    1. Ну PEP же есть. Любой pycharm вам подстветит, что функции всегда в snake_case должны быть. Uppercase только нейминг классов. А у вас все наоборот. PEP не случайно придуман, при импорте всегда понятно что вы импортируете.

    2. Объявление атрибута за пределами __init__ плохая практика. В вашем кейсе оно ничего конечно не сломает, но вы же пытаетесь научить людей. А такому учить не надо.

    3. Ну логика в __init__ тоже как бы не очень. Немного нелогично делать основные вычисления при инициализации объекта, особенно, если там еще и exception свалится может.

    4. Зачем else если после if вы делаете return? Лапша if-else всегда плохо читается.

    5. Зачем вам там вообще классы? вам достаточно 2х функций, которые будут возвращать tuple, ну или namedtuple, если очень хочется. Ну это дело вкуса больше, но классы имеют overhead.

    6. # -*- coding: utf-8 -*- да не нужно уже это. вы что на 2,7 пишете?

    7. Да и про numpy правильно писали. Не делают нормальные люди числовые операции на python листах. Это некрасиво (циклы) и медленно.

    • Replikant_mih
      12 мая 2020, 11:36

      day0markets, вы нативно разраб или работая с прикладными задачами так поднатаскались?))

      2. Это типа когда первый раз поле класса первые раз упоминается (присвоение) вне __init__()? — Интуитивно пришел к тому, что это не айс. Т.е. даже если при инициализации значение не известно надо типа заглушку поставить — а-ля None? — Так выглядит правильная практика?

      4. Какая альтернатива есть else-if лапше? Даже свичей нет в питоне, как я понимаю.

       

      • day0markets.ru
        12 мая 2020, 11:46

        Replikant_mih, разраб + прикладных много + собеседований провел немало)

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

        4. Уменьшить вложенность по возможности. Switch та же хрень в целом. Я два подхода использую:

        -быстрая проверка на значение которое можно сразу вернуть или пробросить exception и потом уже тело функции без else

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

        • Replikant_mih
          12 мая 2020, 11:56

          day0markets, 2. Дефолт — типа пустое значение соответствующего типа, если ожидается словарь — {}, если список — [] и т.д. — я как-то так делаю).

          typing — это про аннотацию типов? — аннотация типов классная штука, недавно открыл. Единственное, придумал как замутить аналог перечислений через конструкции вида:

          class A:

             a = 'a'

             b = 'b'

           

          Но в аннотации конечно же var = A.a — это строка, а хочется: var: A. )

           

          4. После «вынесем в функцию» когда начались «словарики и колбеки» перестал понимать, о чем речь)).

      • Михаил
        12 мая 2020, 12:24
        Replikant_mih, по пункту 2: тут основная проблема в том, что класс по существу не нужен, как правильно написали, поэтому не понятно, как инициализировать. Если класс был бы обоснован, то можно было бы ответить про вопрос инициализации.
      • day0markets.ru
        12 мая 2020, 11:52
        3Qu, да мне то снимать ничего не надо. просто вы людей учите, а учите плохо. и зря к советам не прислушиваетесь, когда вам после ревью говорят, что плохо. так можно годы писать уродливый, низкопроизводительный код и вы сами даже об этом знать не будете. не зря ж придумали код ревью, далеко не бесполезная вещь.
        • Kapeks
          12 мая 2020, 11:59
          day0markets, 
          можно годы писать уродливый, низкопроизводительный код

          ну так почти все программистишки и пишут говнокод. что тут нового?
    • aks19
      12 мая 2020, 17:48
      day0markets, единственное, с чем не согласен — это пункт 7.
      В production, конечно, нормальные люди числодробительные операции выносят во что-нибудь. Но на этапе обучения разве они сразу сходу numpy изучают? :)
  • Ëжик
    12 мая 2020, 11:54
    Формулы нечитаемы, особенно во второй строке, там вообще месиво какое-то. Воспользуйтесь сервисом для написания формул. Например:

      • Ëжик
        12 мая 2020, 12:02
        3Qu, Хорошо, вот что вы тут написали?
         b1 = T/(2*pi)/(1+T/(2*pi))
        Два знака деления, непонятно для последней скобки это числитель или знаменатель.
          • Ëжик
            12 мая 2020, 12:19
            3Qu, не я не про ту скобку. Из вашей записи неясно что вы делите на (1+T/(2*pi)), T/(2*pi) или только (2*pi).
              • Ëжик
                12 мая 2020, 12:35
                3Qu, меняет, теперь запись однозначна, вы же там не кусок кода писали для которого известна логика его распарсивания, а математическую формулу. 

                она же, только в упрощённом виде (в том виде в котором и надо записывать в код):

                Не вижу объективных причин не использовать сервисы для написания формул.
                • ch5oh
                  12 мая 2020, 14:16

                  v_0ver, уважаемый, Вы за год написали 5 топиков по 1 абзацу в каждом. А от благородного 3Qu требуете оформление как в рецензируемом журнале.

                   

                  Написали бы пару содержательных топиков по 5-10 килобайт каждый. Вставили в них формулы красивые и графики интересные. Да хотя бы на тему "Показываю грааль (на Питоне)". Сообщество было бы Вам благодарно и аплодировало. Ругать мы все мастера. А надо своим примером, своим примером.

  • Kapeks
    12 мая 2020, 11:56
    сколько я уже этих скриптовых языков повидал...
    раньше был перл, пхп, яваскрипт, рабби какой то. теперь питон бля.
    всё это мода. а по сути все эти «языки» — обычный бейсик.
    онли с++ вечен, пацаны.
    аминь.
      • Kapeks
        12 мая 2020, 12:05
        3Qu, ты неправильно понимаешь слово моделирование. на си как раз и моделируют всё что угодно, от физики нейтронных звёзд до военных конфликтов. то что простому алготрейдуну надо — так это спец пакет заточенный под создание мтс. там уже вся обвязка сделана, только алгоритмы подставляй.
          • Kapeks
            12 мая 2020, 13:57
            3Qu, мне виднее. тут ты прав.
          • Гденьги ☭
            12 мая 2020, 19:46
            3Qu, так это нормальная практика — накидать рыбу на языке высокого уровня, а потом переписать ради быстродействия.
  • Валентин
    12 мая 2020, 12:40
    Может лучше scipy.signal использовать, чем делать фильтры руками?
  • _sg_
    12 мая 2020, 13:44
    Спасибо.
    Мне понравилось, может быть и я слезу с дивана и начну питон изучать.
    Пишите еще.
  • Serg
    12 мая 2020, 15:24
    Мне тоже понравилось. Поддерживаю топик. Интересно.
  • Мальчик buybuy
    12 мая 2020, 17:02
    Сорри — а зачем это вообще писать на Питоне?

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

    С уважением

    P.S. Ни в одной МА с тюнингом, ни в наборе МА Грааля нет…
  • Пафос Респектыч
    12 мая 2020, 17:06
    Как видим, все работает и идеально откалибровано, что и следовало ожидать от правильно построенного фильтра.

    Только у вас у F40 и FM40 временной лаг будет разный. Не то чтобы это неправильно, но это надо учитывать и чётко осознавать почему мы делаем именно так а не иначе.

    Просто на рынке запаздывание индикатора более важная характеристика чем то какие частоты он фильтрует ) Частот-то строго говоря каких-то особенных нет )
      • Пафос Респектыч
        12 мая 2020, 17:33
        3Qu, дело не в минимальности задержек, а в том чтобы знать какие они и почему они именно такие. Если использовать различные индикаторы, то согласованность их запаздываний может быть важна и на это надо обращать внимание.

        Так я сам пользую в основном EMA разных периодов, как раз именно потому что у них есть понятный естественный фильтрующий смысл.
          • Пафос Респектыч
            12 мая 2020, 18:01
            3Qu, а зачем так загоняться по «нормированию»? Это ж не радио и не аудио ))) так из любви к искусству?
              • Пафос Респектыч
                12 мая 2020, 18:45

                3Qu, ну это некий волюнтаризм, но ладно, предположим.

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


                У EMA по вашей формуле при параметре 40 лаг будет где-то 6.5, а у SMA ровно 20, неслабая такая разница, а глядя на параметр можно подумать что это примерно одно и то же, если в формулу не посмотреть.


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

                  • Пафос Респектыч
                    12 мая 2020, 18:57
                    3Qu, в смысле не EMA? У вас в коде формула EMA, в точности. И операции в методе Filter очевидно выполняются на каждую итерацию цикла.

                    Может быть вы думаете что EMA это что-то другое, но это оно и есть что у вас написано, просто деление на 2pi добавлено.
                      • Пафос Респектыч
                        12 мая 2020, 19:09
                        3Qu, EMA — это просто взвешенная сумма, у которой веса убывают экспоненциально. У вас она и есть, просто вы параметр экспоненты на 2pi поделили, но что так веса экспоненциально убывают, что эдак ))
                          • Пафос Респектыч
                            12 мая 2020, 19:19
                            3Qu, да я то ничего не считаю, я просто смотрю что у вас в коде написано )
  • JoeFox
    12 мая 2020, 21:34
    3Qu, спасибо, большое! Отличная статья, продолжать писать пожалуйста!
  • Евгений Шибаев
    13 мая 2020, 00:36
    Я конечно извиняюсь, коллега, пост о том как начать программировать на Питоне. Но тем кто уже умеет можно короче — в одну строку (я про ЕМА)
    Пример 1 минутного графика RIM0 за 12 мая 2020 с ЕМА-21

    import pandas as pd
    import matplotlib.pyplot as plt
    #Читаем в датафрейм candles минутки RIM0 за 12 мая 2020. Загружены с Финама, лежат в «загрузках» в текстовом формате
    candles=pd.read_csv('C:\\Users\\User\\Downloads\\SPFB.RTS-6.20_200512_200512.txt', header=0, sep=';')
    #Создаем столбец 'ema21' применяя ЕМА к цене закрытия (столбец '<CLOSE>')
    candles['ema21'] = pd.Series.ewm(candles['<CLOSE>'], span=21).mean() #собственно расчет ЕМА-21
    #Отрисовываем цену закрытия и ЕМА-21
    plt.figure(figsize=(15,5))
    plt.plot(candles['<CLOSE>'])
    plt.plot(candles['ema21'])
    plt.show()

    Результат:

    Используем библиотеку pandas.
    Тимофей, чего у тебя код в комменты нельзя вставить, только в топик?

    • i aztec
      12 августа 2020, 15:42
      Евгений Шибаев, и рисовать тоже можно в одну строку :)

      candles.plot(y=['<CLOSE>', 'ema21'], figsize=(15,5))
  • Value
    13 мая 2020, 01:50
    Спасибо.
  • Алексей
    13 мая 2020, 09:58
    Что скажите про авиатор 2020 года? стоит брать?
  • badpidgin
    13 мая 2020, 15:27
    Можно убрать одну проверку и чуть упростить код
    def Filter(self, x):
            a0 = 1/(1+self.T/(2*math.pi))
            b1 = self.T/(2*math.pi)/(1+self.T/(2*math.pi))
            self.out = [x[0]]
            for i in range(1, len(x)):
                self.out.append(a0*x[i]+b1*self.out[i-1])
    
  • Ынвестор
    21 мая 2020, 19:43
    Ребяты, вы прикалываетесь? TaLib на что вам даден?
      • Ынвестор
        21 мая 2020, 22:01
        3Qu, я так понял. Велосипед с нуля придумывать ужас как интересно 
  • Павел Новиков
    20 января 2021, 20:11
    Добрый вечер, спасибо за ваши статьи, познавательно) 
    Перейду к сути, есть индикатор на Lua, но как понял в Квике бэктест не возможен.Скажите пожалуйста, на Питоне вы делаете бэктест? есть такая возможность.

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн