Евгений Шибаев
Евгений Шибаев личный блог
04 ноября 2020, 20:16

Визуализация рекомендаций Романа Андреева на Python

Доброго всем здоровья и веселого праздника!

В этом топике я покажу как на Питоне можно извлекать полезную информацию из обычного текста и представлять ее на графиках. Большинство аудитории Смартлаба знают Романа Андреева (2 место по рейтингу, после Создателя) как профессионального трейдера, рекомендациями которого пользуются многие смартлабовцы. Ежедневный утренний топик «Ситуация на текущий момент», стал уже многолетней традицией, как чашка кофе с круассаном, и по-праву набирает огромное количество лайков. Его рекомендации помогают людям не только сохранить свой капитал, но и приумножить его. Я, к сожалению, лично не знаком с Романом, но давно являюсь его подписчиком. А еще, мне нравятся его стихи!
Спасибо Роману за его труд! Я же, постараюсь добавить «наглядности» рекомендациям с помощью кода на Питоне, как всегда в несколько строк.
Визуализация рекомендаций Романа Андреева на Python
Итак, за дело! Топик длинный и н ам понадобятся:

1. Любая среда Python (например, Jupyter Notebook Anaconda 3)
2. Вы должны быть подписаны на блог Романа Андреева, т.е. быть его другом на Смартлабе, т.к. доступ к публикации имеют только друзья. Если вы пока не в их числе, то здесь найдете ссылку на 7 прошлых рекомендаций для тестирования кода.
В статье (из кода) вы узнаете:
а) Как добывать исторические котировки по любым инструментам с сайта Финама.
б) Извлекать из обычного текста нужную информацию с помощью регулярных выражений (RE) Python.
в) Представлять извлеченные данные на графиках Matplotlib

Почти каждое утро я на начинаю с чтения блога Романа Андреева «Ситуация на текущий момент». Пока западная часть нашей необъятной Родины находится в объятиях Морфея (короче, спят еще) Роман анализирует за нас ситуацию на мировых площадках и публикует после 7 утра по МСК рекомендации и уровни по основным инструментам финансового рынка: индексам S&P и ММВБ, нефти, золоту, евро-доллару и рубль-доллару. Рекомендация представляет из себя текст, в котором обозначены важные ценовые уровни, зоны поддержки и сопротивления, а также действия в случае развития того или иного сценария. Выглядит примерно так:
Визуализация рекомендаций Романа Андреева на Python
Скопируем содержание рекомендации и сохраним его в текстовом файле с названием ГГГГММДД.txt в папку (в нашем коде C:\RomanAndreev), где будет база данных рекомендаций на каждый день. Например, в файле «20201103.txt» будет текст вчерашнего топика.
Чтобы нарисовать график, например СиПи, нам нужно откуда-то взять данные для него. На сегодня такую возможность предоставляет Финам (а мы всё ругаем его). Поэтому, первым делом научимся качать котировки с сайта Финама в Питон. За основу я взял код из топика «Качаем котировки с Финама», который любезно предоставил наш коллега Albus (Игорь Китаев), немного переделав его под собственные нужды. Основная функция GetCandles (ticker, time_frame, period_days), загружает в датафрейм котировки по инструменту ticker, для заданного таймфрейма за последние period_days. Например, вызов micex=GetCandles ('MICEX', «15min», 10) вернет в переменную micex датафрейм, содержащий 15-ти минутные свечи индекса ММВБ за последние 10 дней, начиная с текущего момента времени.
Визуализация рекомендаций Романа Андреева на Python
Код майнера, с максимально возможными комментариями внизу: 

############################## Мoдуль извлечения котировок с сайта www.finam.ru ###########################################
from urllib.parse import urlencode
from urllib.request import urlopen
from datetime import datetime, timedelta
import pandas as pd

FINAM_URL = "http://export.finam.ru/" # сервер, на который стучимся
#каждому таймфрейму на Финаме соответствует цифровой код:
periods={'tick': 1, 'min': 2, '5min': 3, '10min': 4, '15min': 5, '30min': 6, 'hour': 7, 'daily': 8, 'week': 9, 'month': 10}
#каждому символу Финам присвоил цифровой код:
symbols={'S&P':13944, 'USDRUB': 901, 'ED':83, 'GD':18953, 'MICEX': 420450, 'BZ': 19473, 'ABRD':82460,'AESL':181867,'AFKS':19715,'AFLT':29,'AGRO':399716,'AKRN':17564,'ALBK':82616,'ALNU':81882,'ALRS':81820,'AMEZ':20702,'APTK':13855,'AQUA':35238,'ARMD':19676,'ARSA':19915,'ASSB':16452,'AVAN':82843,'AVAZ':39,'AVAZP':40,'BANE':81757,'BANEP':81758,'BGDE':175840,'BISV':35242,'BISVP':35243,'BLNG':21078,'BRZL':81901,'BSPB':20066,'CBOM':420694,'CHEP':20999,'CHGZ':81933,'CHKZ':21000,'CHMF':16136,'CHMK':21001,'CHZN':19960,'CLSB':16712,'CLSBP':16713,'CNTL':21002,'CNTLP':81575,'DASB':16825,'DGBZ':17919,'DIOD':35363,'DIXY':18564,'DVEC':19724,'DZRD':74744,'DZRDP':74745,'ELTZ':81934,'ENRU':16440,'EPLN':451471,'ERCO':81935,'FEES':20509,'FESH':20708,'FORTP':82164,'GAZA':81997,'GAZAP':81998,'GAZC':81398,'GAZP':16842,'GAZS':81399,'GAZT':82115,'GCHE':20125,'GMKN':795,'GRAZ':16610,'GRNT':449114,'GTLC':152876,'GTPR':175842,'GTSS':436120,'HALS':17698,'HIMC':81939,'HIMCP':81940,'HYDR':20266,'IDJT':388276,'IDVP':409486,'IGST':81885,'IGST03':81886,'IGSTP':81887,'IRAO':20516,'IRGZ':9,'IRKT':15547,'ISKJ':17137,'JNOS':15722,'JNOSP':15723,'KAZT':81941,'KAZTP':81942,'KBSB':19916,'KBTK':35285,'KCHE':20030,'KCHEP':20498,'KGKC':83261,'KGKCP':152350,'KLSB':16329,'KMAZ':15544,'KMEZ':22525,'KMTZ':81903,'KOGK':20710,'KRKN':81891,'KRKNP':81892,'KRKO':81905,'KRKOP':81906,'KROT':510,'KROTP':511,'KRSB':20912,'KRSBP':20913,'KRSG':15518,'KSGR':75094,'KTSB':16284,'KTSBP':16285,'KUBE':522,'KUNF':81943,'KUZB':83165,'KZMS':17359,'KZOS':81856,'KZOSP':81857,'LIFE':74584,'LKOH':8,'LNTA':385792,'LNZL':21004,'LNZLP':22094,'LPSB':16276,'LSNG':31,'LSNGP':542,'LSRG':19736,'LVHK':152517,'MAGE':74562,'MAGEP':74563,'MAGN':16782,'MERF':20947,'MFGS':30,'MFGSP':51,'MFON':152516,'MGNT':17086,'MGNZ':20892,'MGTS':12984,'MGTSP':12983,'MGVM':81829,'MISB':16330,'MISBP':16331,'MNFD':80390,'MOBB':82890,'MOEX':152798,'MORI':81944,'MOTZ':21116,'MRKC':20235,'MRKK':20412,'MRKP':20107,'MRKS':20346,'MRKU':20402,'MRKV':20286,'MRKY':20681,'MRKZ':20309,'MRSB':16359,'MSNG':6,'MSRS':16917,'MSST':152676,'MSTT':74549,'MTLR':21018,'MTLRP':80745,'MTSS':15523,'MUGS':81945,'MUGSP':81946,'MVID':19737,'NAUK':81992,'NFAZ':81287,'NKHP':450432,'NKNC':20100,'NKNCP':20101,'NKSH':81947,'NLMK':17046,'NMTP':19629,'NNSB':16615,'NNSBP':16616,'NPOF':81858,'NSVZ':81929,'NVTK':17370,'ODVA':20737,'OFCB':80728,'OGKB':18684,'OMSH':22891,'OMZZP':15844,'OPIN':20711,'OSMP':21006,'OTCP':407627,'PAZA':81896,'PHOR':81114,'PHST':19717,'PIKK':18654,'PLSM':81241,'PLZL':17123,'PMSB':16908,'PMSBP':16909,'POLY':175924,'PRFN':83121,'PRIM':17850,'PRIN':22806,'PRMB':80818,'PRTK':35247,'PSBR':152320,'QIWI':181610,'RASP':17713,'RBCM':74779,'RDRB':181755,'RGSS':181934,'RKKE':20321,'RLMN':152677,'RLMNP':388313,'RNAV':66644,'RODNP':66693,'ROLO':181316,'ROSB':16866,'ROSN':17273,'ROST':20637,'RSTI':20971,'RSTIP':20972,'RTGZ':152397,'RTKM':7,'RTKMP':15,'RTSB':16783,'RTSBP':16784,'RUAL':414279,'RUALR':74718,'RUGR':66893,'RUSI':81786,'RUSP':20712,'RZSB':16455,'SAGO':445,'SAGOP':70,'SARE':11,'SAREP':24,'SBER':3,'SBERP':23,'SELG':81360,'SELGP':82610,'SELL':21166,'SIBG':436091,'SIBN':2,'SKYC':83122,'SNGS':4,'SNGSP':13,'STSB':20087,'STSBP':20088,'SVAV':16080,'SYNG':19651,'SZPR':22401,'TAER':80593,'TANL':81914,'TANLP':81915,'TASB':16265,'TASBP':16266,'TATN':825,'TATNP':826,'TGKA':18382,'TGKB':17597,'TGKBP':18189,'TGKD':18310,'TGKDP':18391,'TGKN':18176,'TGKO':81899,'TNSE':420644,'TORS':16797,'TORSP':16798,'TRCN':74561,'TRMK':18441,'TRNFP':1012,'TTLK':18371,'TUCH':74746,'TUZA':20716,'UCSS':175781,'UKUZ':20717,'UNAC':22843,'UNKL':82493,'UPRO':18584,'URFD':75124,'URKA':19623,'URKZ':82611,'USBN':81953,'UTAR':15522,'UTII':81040,'UTSY':419504,'UWGN':414560,'VDSB':16352,'VGSB':16456,'VGSBP':16457,'VJGZ':81954,'VJGZP':81955,'VLHZ':17257,'VRAO':20958,'VRAOP':20959,'VRSB':16546,'VRSBP':16547,'VSMO':15965,'VSYD':83251,'VSYDP':83252,'VTBR':19043,'VTGK':19632,'VTRS':82886,'VZRZ':17068,'VZRZP':17067,'WTCM':19095,'WTCMP':19096,'YAKG':81917,'YKEN':81766,'YKENP':81769,'YNDX':388383,'YRSB':16342,'YRSBP':16343,'ZHIV':181674,'ZILL':81918,'ZMZN':556,'ZMZNP':603,'ZVEZ':82001}

# Функция запрашивает котировки с сервера экспорта данных Финама по инструменту для заданного таймфрейма за последние 
# period_days дней и возвращает соответствующий датафрейм
def GetCandles (ticker, time_frame, period_days):
    period=periods[time_frame] #Выбор из: 'tick': 1, 'min': 2, '5min': 3, '10min': 4, '15min': 5, '30min': 6, 'hour': 7, 'daily': 8, 'week': 9, 'month': 10
    market = 0 #можно не задавать. Это рынок, на котором торгуется бумага. Для акций работает с любой цифрой. Другие рынки не проверял.
    # Текущий момент времени
    end_date = datetime.today()
    # Время period_days дней назад
    start_date = end_date - timedelta(days = period_days)
    #Все параметры упаковываем в единую структуру. Здесь есть дополнительные параметры, кроме тех, которые заданы в шапке. См. комментарии внизу:
    params = urlencode([
     ('market', market), #на каком рынке торгуется бумага
     ('em', symbols[ticker]), #вытягиваем цифровой символ, который соответствует бумаге.
     ('code', ticker), #тикер нашей акции
     ('df', start_date.day), #Начальная дата, номер дня (1-31)
     ('mf', start_date.month - 1), #Начальная дата, номер месяца (0-11)
     ('yf', start_date.year), #Начальная дата, год
     ('from', start_date), #Начальная дата полностью
     ('dt', end_date.day), #Конечная дата, номер дня
     ('mt', end_date.month - 1), #Конечная дата, номер месяца
     ('yt', end_date.year), #Конечная дата, год
     ('to', end_date), #Конечная дата
     ('p', period), #Таймфрейм
     ('f', ticker), #Имя сформированного файла
     ('e', ".csv"), #Расширение сформированного файла
     ('cn', ticker), #ещё раз тикер акции
     ('dtf', 1), #В каком формате брать даты. Выбор из 5 возможных. См. страницу https://www.finam.ru/profile/moex-akcii/sberbank/export/
     ('MSOR', 0), #Время свечи (0 - open; 1 - close)
     ('mstime', "on"), #Московское время
     ('mstimever', 1), #Коррекция часового пояса
     ('sep', 1), #Разделитель полей (1 - запятая, 2 - точка, 3 - точка с запятой, 4 - табуляция, 5 - пробел)
     ('sep2', 1), #Разделитель разрядов
     ('datf', 1), #Формат записи в файл. Выбор из 6 возможных.
     ('at', 1)]) #Нужны ли заголовки столбцов
    url = FINAM_URL + ticker + ".csv?" + params #собственно URL сформированного запроса
    #Создаем датафрейм candles с котировками
    candles = pd.read_csv(url)
    #Добавляем в датафрейм столбец 'DT', который будет содержать время каждой свечи в формате datetime. 
    #Формируем его из столбцов '<DATE>'и '<TIME>', их в последствие можете удалить
    candles['DT'] = list(map(lambda d,t: ToDatetime(d,t), candles['<DATE>'], candles['<TIME>']))
    #Возвращает Датафрейм Пандас со свечами, соответствующими запросу
    return candles

#Преобразует число (или строку) вида 20201030 и строку вида '12:15:00' в объект datetime.datetime(2020, 10, 30, 12, 15)
def ToDatetime (date_num, time_hhmmss):
    return datetime.strptime(str(date_num) + time_hhmmss, '%Y%m%d%H:%M:%S')

#Преобразует строку (или число) вида "20201102" в дату (формат datetime)
def ToDate (date_yyyymmdd):
    return datetime.strptime(str(date_yyyymmdd), '%Y%m%d').date()
Прекрасно, данные с Финама тянем. Переходим к разборке текста рекомендаций. Мы не будем применять сложные алгоритмы разбора текста на корпуса или прибегать к помощи нейронных сетей, чтобы понять его смысл, а воспользуемся регулярными выражениями (RE) в Питоне, для поиска нужной нам информации. Если посмотреть на текст, то он разбит на абзацы, в которых (но не в каждом) описывается ситуация по тому или иному инструменту (нефти, золоту, индексам и.т.д). Значит первым делом нам нужно понять о каком тикере в абзаце идет речь. Используем функцию similarity(s1, s2) из библиотеки difflib, чтобы определить схожесть одной строки с другой. Одной из строк будут наши токены ('ММВБ',  'Золото',  'СиПи',  'Нефть',  'Евро-доллар',  'Доллар-рубль'), а строками для сравнения первые четыре фразы каждого абзаца. Если схожести нет совсем, то в этом абзаце не упоминаются интересующие нас тикеры и мы пропускаем его. Код реализован в функции what_ticker_about(text).
Теперь, когда мы поняли о каком тикере будет абзац, нужно выделить значения уровней и зон. Уровни представлены в виде чисел, не только целых, как например индекс ММВБ 2770, но и с десятичной точкой, например нефть по 38,88 — причем вместо точки в тексте может быть запятая. Для поиска таких чисел используем регулярное выражение re.findall(r'\d+\,\d+|\d+', sentence), которое ищет в предложении sentence все числа, стоящие перед запятой и сразу после запятой, или просто отдельно стоящие числа и возвращает список из них. Вы можете сами потренироваться, подавая различные строки из текста на вход этой функции, чтобы детально понять как она работает. Таким образом определяются уровни. Подобным образом написана функция find_zones для определения зон (с верхней и нижней границами цен). Все функции для работы с текстом собраны в разделе «Помощники при семантическом;) разборе предложения для поиска ценовых уровней».
Уровни и зоны найдены, нужно их отобразить на графике, с использованием библиотеки matplotlib. Работу выполняют всего четыре функции draw_candles — рисует сами графики, draw_levels — уровни, draw_zones — зоны и sign_levels подписывает значения уровней и отображает текст рекомендации прямо на графике. Код буквально напичкан комментариями, если какая строка не понятна — пишите — объясню.
И наконец, главная функция, которая запускает весь код start_function(). В ней вы можете задать параметры отображения графиков, например таймфрейм, количество торговых сессий, количество дней с рекомендациями и т.д. Функция выполнит следующую работу: скачает с сайта Финама котировки на текущий момент времени по указанным тикерам, найдет в файлах рекомендаций по датам ценовые уровни и зоны, определенные Романом для каждого тикера и отобразит их по дням на графиках.
Код для раздела анализа текста и визуализации представлен ниже:
import difflib
import re 
import time
from glob import glob
import os
import matplotlib.pyplot as plt
from textwrap import wrap

#В папке path должны лежать все файлы с рекомендациями от Романа Андреева (и не только) по датам 
#в файлах с именами ГГГГММДД.txt в обычном текстовом формате, например '20201103.txt'
path = 'C:\\RomanAndreev\\'

#------------------кусок файла приведен ниже:-------------------------------------------------------
#Вчера индекс ММВБ закрыл день белой свечкой. Отбившись вниз от низа своего осн канала (на утро 2686)
#он выполнил свою первую цель в лице низа более локального канала (на утро 2660), проколов уровень на
#...
#СиПи подрастает и должен потестить свои сопротивления (на утро — зона 3333-3336): отбой оттуда продаем 
#с целями 3232, 3168 и 3106, пробой с ретестом покупаем с целями 3402 и 3420. Закрепимся выше 3420  — обновим истхаи.
#
#Евро-доллар застопорил свое снижение и может потестить снизу пробитые поддержки (на утро 1,1713 и 1,1744):
#отбой оттуда или пробой недавнего лоя продаем до зоны 1,1535-1,1502, пробой с ретестом снова покупаем до 1,1942 и 1,2152.
#...

#Список инструментов, которые анализирует Роман. Тикер - токен: тикер это торгуемый актив, а токен - его привычное название.
# Ключ в словаре tickers ДОЛЖЕН СТРОГО СООТВЕТСТВОВАТЬ ключу в словаре symbols (в модуле загрузки котировок с Финама)
tickers = {'MICEX' : 'ММВБ',
           'GD' : 'Золото', 
           'S&P' : 'СиПи',
           'BZ' : 'Нефть',
           'ED' : 'Евро-доллар',
           'USDRUB' : 'Доллар-рубль'}

######################## Помощники при семантическом;) разборе предложения для поиска ценовых уровней ####################
skip_nums = ["1", "2", "3", "4", "5"] #Эти числа мы исключаем из анализа, если только цена актива не находится рядом с ними

#Функция, определяющаа похожесть строк s1 и s2. Чем болше строки похожи друг на друга тем значение будет ближе к 1, иначе к 0
def similarity(s1, s2):
    matcher = difflib.SequenceMatcher(None, s1.lower(), s2.lower())
    return matcher.ratio()

#Функция, вернет тикер из списка tickers, если в строке text, в первых четырех словах встречается близкое совпадение
#с названием тикера, иначе возвращает False. Т.е. определяет о каком тикере в предложении идет речь.
def what_ticker_about (text):
    words4 = re.split(' ', text, maxsplit=4)[:4]
    for ticker in tickers:
        if max(map(lambda w: similarity(w, tickers[ticker]), words4)) > 0.67:
            return ticker
    return False

#Функция находит в предложении sentence все значимые числа, которые, как мы считаем, могут представлять ценовые уровни,
# при этом исключая часто встречаемые числа, например "1-я волна", "2 раза" и т.д. Заодно меняем в числовых значениях
# запятые на точки и возвращаем список значений в формате float.
def find_levels (sentence):
    levels = re.findall(r'\d+\,\d+|\d+', sentence)
    levels_str = [level for level in levels if level not in skip_nums] 
    return list(map(lambda x: float(x.replace(",", ".")), levels_str))

#Определяет диапазоны цен, возвращает список пар (нижняя-верхняя границы диапазона): [['38.5', '38.65'], ['40.25', '40.38']]
def find_zones (sentence):
    zones = re.findall(r'\d+\-\d+|\d+\,\d+\-\d+\,\d+', sentence)
    return list(map(lambda x: x.replace(",", ".").split('-'), zones))

#################################### Помощники для построения графиков ##########################################
#Определяет начальную и конечную позицию Х (по индексу свечей) для заданной даты. Пригодится при отрисовке ценовых уровней
def DateX (date, candles):
    #Цикл по датам в свечах, результат - список X-координат, соответствующих заданной дате
    xpositions = [index for index, row in candles.iterrows() if row['DT'].date() == date]
    #Возвращает список - пару начальная координата Х и конечная координата Х для заданной даты на графике
    if xpositions == []:
        return [len(candles)-1, len(candles)] #На случай если за текущую дату нет еще свечей
    return [xpositions[0], xpositions[-1]]

#Рисует метки дат на оси Х
def PlotDatesX (fig, candles):
    #Составляем список дат (только уникальные даты) из столбца DT. Они будут метками на оси Х. Сортировка по датам
    #обязательна, т.к. при создании множества(set) даже из отсортированного списка, множество может не сохранить порядок списка
    dates = sorted(set(map(lambda dt: datetime.date(dt), candles['DT'])))
    #Создаем список координат Х для каждой метки (даты). Нам нужна только первая позиция - [0].
    xlabel = [DateX(d, candles)[0] for d in dates]
    #Рисуем ось Х, разделенную по датам
    fig.set_xticklabels(dates)
    fig.set_xticks(xlabel)
    return dates, xlabel

#Рисует основной график
def draw_candles(candles):
    #Добавим на график несколько ЕМА-средних
    candles['ema100'] = pd.Series.ewm(candles['<CLOSE>'], span=100).mean()
    candles['ema50'] = pd.Series.ewm(candles['<CLOSE>'], span=50).mean()
    candles['ema20'] = pd.Series.ewm(candles['<CLOSE>'], span=20).mean()
    plt.style.use('ggplot') #'seaborn-paper'
    #Отображаем график по цене закрытия свечей и ЕМА-шки
    fig = candles.plot(y=['<CLOSE>', 'ema50', 'ema20', 'ema100'], figsize=(25,16))
    #Добавляем заголовок
    fig.set_title('График ' + candles['<TICKER>'][0])
    #Рисуем шкалу с датами
    PlotDatesX (fig, candles)

#Рисует горизонтальные уровни цен на основном графике, соответствующие уровням рекомендаций на заданную дату
def draw_levels (datetime_date, candles, levels):
    for level in levels:
        #Левая и правая Х-координата для даты на графике [x1, x2]
        DX = DateX(datetime_date, candles)
        plt.plot(DX, [level, level], color = 'dimgray', linewidth = 0.8)

#Рисуем зоны на основном графике, соответствующие диапазонам рекомендаций на заданную дату
def draw_zones (datetime_date, candles, zones):
    for zone in zones:
        #Левая и правая Х-координата для даты на графике [x1, x2]
        DX = DateX(datetime_date, candles)
        plt.fill([DX[0], DX[0], DX[1], DX[1]], [float(zone[0]), float(zone[1]), float(zone[1]), float(zone[0])], alpha = 0.3)
        
#Функция завершает отрисовку "сегодняшних" ценовых уровней, подписывая их значения, а также располагает текст
#рекомендации на графике. На вход принимает свечи, уровни и текст рекомендации advice
def sign_levels (candles, levels, advice):
    x = len(candles) #Координата Х правого края графика
    for level in levels:
        plt.text(x+3, level, str(level), color = 'white', verticalalignment='center', bbox={'facecolor': 'dimgray', 'pad': 2})
    price = candles.iloc[-1]['<CLOSE>']
    plt.text(x+3, price, str(price), color = 'white', verticalalignment='center', bbox={'facecolor': 'orange', 'pad': 2})
    #Пытаемся красиво разместить текст рекомендации. Разобьем ее (рекомендацию) на строчки по 98 символов
    wrapped = '\n'.join(wrap(advice, width=98))
    #Сам текст рекомендации разместим на минимальном по значению уровне и в левой части графика
    text_ypos = min(levels)
    plt.text(3, text_ypos, wrapped, fontsize=15, color='dimgray', bbox={'facecolor': 'white', 'alpha': 0,'pad': 2})

#Основная функция для запуска. В папке path должны лежать все файлы с рекомендациями от Романа Андреева (и не только)
#по датам в файлах с именами ГГГГММДД.txt в обычном текстовом формате
def start_function(path):
    days_with_levels = 7 #за какое количество дней назад от сегодняшнего показывать уровни рекомендаций
    days_for_chart = 30 #за какое количество дней строить общий график
    time_frame = '30min' #какой тайм-фрейм использовать для графика
    #Проход по всем тикерам в списке рекомендаций
    for ticker in tickers:
        last_levels = [] #в этой переменной после окончания цикла будут уровни текущего дня, которые мы подпишем на графике
        last_advice = '' #а в этой переменной рекомендации на текущий день
        #Читаем в датафрейм candles свечки с сайта Финама
        candles = GetCandles (ticker, time_frame, days_for_chart)
        time.sleep(0.5) #делаем небольшую задержку в запросах к серверу котировок, чтобы нас Финам не забанил
        draw_candles(candles)
        #В этой папке path должны лежать все файлы с рекомендациями от Романа Андреева по датам в формате ГГГГММДД.txt
        for file in sorted(glob(f'{path}\\*.txt'))[-days_with_levels:]:
            with open(file, 'r') as f:
                current_date = os.path.splitext(os.path.basename(file))[0]
                #Читаем файл по абзацам, исключая пустые строки
                indent = [line.strip() for line in f if line.strip()]
                #Разбираем абзац на предложения
                for sentence in indent:
                    #Если находим упоминание тикера в предложениях абзаца
                    found_ticker = what_ticker_about(sentence)
                    if found_ticker == ticker:
                        #Определяем уровни цен для тикера за эту дату
                        levels = find_levels(sentence)
                        zones = find_zones(sentence)
                        last_levels = levels
                        last_advice = sentence
                        print ("Proceccing...", current_date, ticker)
                        draw_levels(ToDate(current_date), candles, levels)
                        draw_zones(ToDate(current_date), candles, zones)
        #Подписываем уровни "свежих" (сегодняшних) рекомендаций
        sign_levels(candles, last_levels, last_advice)

#Запуск!        
start_function(path)        
Итак, подведем итог. Алгоритм действий следующий:
1. Заходите на Смартлаб в блог Романа Андреева.
2. Копируете текст рекомендаций и сохраняете его в папку, которая прописана в переменной path = 'C:\\RomanAndreev\\' (в нашем случае папка RomanAndreev на диске C:. Имя текстового файла должно соответствовать дате рекомендации, например 20201105.txt
3. Запускаете Питон. Загружаете два куска кода (извлечение котировок с Финама и визуализация). Можете объединить их в один.
4. В Питоне запускаем код Ctrl+Enter
5. Если все сделали правильно, дожидаетесь пока появятся графики по типу вчерашних:
Визуализация рекомендаций Романа Андреева на Python
Визуализация рекомендаций Романа Андреева на Python
и т.д.

Надеюсь эта статья окажется для вас полезной и добавит друзей не только Роману Андрееву, но и мне.
А и ещё, мне конечно очень стыдно, но у меня есть свой канал на ютьюбе (по типу: я такой-то такой то, и я алкоголик… аплодисменты).
Но я не потерянный для общества человек — У МЕНЯ НЕТ КАНАЛА В ТЕЛЕГЕ!

Вообщем всем доброго здоровья!







42 Комментария
  • Геннадий А
    04 ноября 2020, 20:22
    Спасибо за труд!
  • 10Dima
    04 ноября 2020, 20:22
    отлично!  
  • Lop
    04 ноября 2020, 20:23
  • igorD-1
    04 ноября 2020, 20:30
    сильно
  • михаил алексеев
    04 ноября 2020, 20:31
    лысый ждет бакс по 45

    в какой то там волне

    уровень жени чернявого только без миллионов
  • Friendly Deep Space
    04 ноября 2020, 20:37
    Сделайте бэктест на истории за год по его прогнозам, интересно было бы взглянуть)
  • GrayFox
    04 ноября 2020, 23:32
    ой… ржу… ваши алгоритмы рапознавания текса выше аж гуру остальных вне смартлаба?
    Научите меня!

    Проше таблицу  на график, которая часто не меняется при застое ...

    Реально поржал над разбивкой на блоки тексата и прочим!
  • GrayFox
    04 ноября 2020, 23:35
     ещ9ё больше ржу над тем, чтобы просто по памяти не пробежаться по короткой таблице… сравнить со совим с торгуемыми инструментами
    3. Запускаете Питон. Загружаете два куска кода (извлечение котировок с Финама и визуализация). Можете объединить их в один.
    4. В Питоне запускаем код Ctrl+Enter
  • GrayFox
    04 ноября 2020, 23:36
     Придуман графический аналог горизонтального  индикатора… за которым стоит человек с ситемой. но никак е цифровой поток!!!
  • Тимофей Мартынов
    05 ноября 2020, 00:00
    класс!
  • Герман Греф
    05 ноября 2020, 08:26
    Давайте короче, где Сберпреф откупать?
  • Socol
    05 ноября 2020, 10:53
    Очень интересный и полезный пост, спасибо Евгений!
  • MS
    05 ноября 2020, 12:48
    Что делать с ошибками в числах, которые встречаются у РА регулярно. А также необновлением рекомендаций по инструменту по забывчивости.
  • Чёрный Трейдер
    05 ноября 2020, 14:23
    Пишите примеры сразу в Google Colab, это будет выглядеть как статья, сразу код и результат виден.
    Для образовательных целей (типа  этой статьи), лучше Колаба не найти.
  • monte_carlo
    05 ноября 2020, 15:24
    Просыпаешься такой с бодуна, а у тебя установлен Яндекс.Браузер и папка «Роман Андреев» в корне диска С))
    C:\RomanAndreev
  • Zun Ruf
    05 ноября 2020, 17:53
    Ничего не понял)) Но понял что есть на свете умные люди…
  • OUBee
    14 ноября 2020, 00:52
    А практическое применение какое?
  • Igor
    15 ноября 2020, 15:14
    А выкладывай неск графиков как ответ в блог Романа или свой создай, цены тебе не будет! Зачем всем тоже саме переделывать.
    Для начала несколько инструментов, напр сбер, газ, сипи. Три графика всего, будут лайки можно расширить.
      • Igor
        15 ноября 2020, 22:26
        Евгений Шибаев, да рад он будет, что продвигают. Если не понравится то напишет сразу
      • Сергей Брониславович
        05 декабря 2020, 20:18
        Евгений Шибаев, Уровни Романа работают безукоризненно Каналы использовал всегда но на высоких ТФ и честно говоря сначала скептически относился к его рекомендациям по скальперским уровням Но понаблюдав достаточно длительное время должен сказать — просто поразительно иногда пип в пип! Так что этот код большой подарок
        для поклонникам Романа. Пока перенесешь на график уже сессия закончиться :) а тут прямо картинка!
        Евгений спасибо за код, с большим уважением отношусь к ребятам кто умеет делать такие вещи!

        Просьба  -можно подправить код под последние версии Pyton 3.8.2, Pip 20.3.1 и matplotlib до 3.2.3
        У меня что та же ошибка




  • Игнатов Виталий
    30 ноября 2020, 18:35
    Спасибо за статью!
    Подскажите, возникает ошибка следующего характера: 

    UserWarning: FixedFormatter should only be used together with FixedLocator
    fig.set_xticklabels(dates)


    Как бороться?

    • Игнатов Виталий
      30 ноября 2020, 21:30
      Разобрался.

      Кому будет интересно:

      необходимо понизить версию библиотеки matplotlib до 3.2.2
      и если пишите в pycharm указать plt.show() в конце кода
      тогда все будет работать
  • Сергей Брониславович
    05 декабря 2020, 20:43
    Евгений Шибаев, в догонку вопрос — Вы разобрались с этой путаницей в lua5.1.dll 51.dll и т.п. которая возникла после перехода на Quik x64 8.5++ и частности читал что вы socket писали.  Я так и не нашел цивилизованного решения Весь инет пестрит советам типа переименовывать dll из 5.1 квиковского  в 51 Ну маразм какой то  Библиотеку QL никак не могу подключить (QUIK\LuaScripts\QL\QL.lua:66: attempt to index a nil value (global 'socket')
      • Сергей Брониславович
        06 декабря 2020, 00:01
        Евгений Шибаев, Спасибо. Lua 5.3.5 (Binaries) у меня скачен с luabinaries.sourceforge.net/   распакован C:\Program Files\LUA\5.3)
        с библиотекой естественно Там lua53.dll уже есть
        Из пакета  QuikSharp, из каталога src\QuikSharp  перенес QUIK\LuaScripts\QuikSharp  Туда же положил  QUIK\LuaScripts\QL
        QuikSharp.lua работает А попытки загрузить QL приводят либо той ошибке что привел выше либо error loading module 'socket.core' from file 'C:\Program Files\Lua\5.3\clibs\socket\core.dll':
            %1 не является приложением Win32
        Ну QuikSharp.lua подгружает библиотеки QuikSharp\lua\clibs64 а QL идет в
        C:\Program Files Но у меня там точно x64 в сlibs  [clib_luasec-0.6-openssl-1.0.2o-luasocket-3.0-win64.zip] лежит. Так я для проверки в ней package.cpath переписал на каталог из Шарпа Но пока безрезультатно Полазил по инету Таких как я много оказалось…
        Я к Квику вернулся много лет спустя по просьбе сына… сам в IB торгую, так что отстал от жизни :)
  • Роман Давыдов
    10 декабря 2020, 14:28
    Добрый день, можете подсказать что делать с данной ошибкой «ValueError: min() arg is an empty sequence» не силен в программировании?
      • Роман Давыдов
        11 декабря 2020, 10:43
        Евгений Шибаев, Привет, не смог найти твой адрес личной почты. Чтобы отправить файл

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

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