Блог им. jatotrade_com

Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.

Всем здоровья и бодрого расположения духа!
В статье «Визуализация рекомендаций Романа Андреева на Python» мы разобрали как можно с помощью нескольких строк кода на Питоне разобрать текст, который выкладывает каждое утро в своем блоге Роман Андреев (далее по тексту Роман) — известный трейдер и блогер (или наоборот), и отобразить эти рекомендации в виде уровней и зон на графиках. В этом топике я покажу способ для извлечения информации из графических изображений с помощью технологий компьютерного зрения (но без использования нейронных сетей) на примере таблиц-рекомендаций из блога Романа Андреева.
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.
Надеюсь, что я не напугал читателей термином «компьютер вижн», скоро вы поймете, что это просто. И что любой юный прогер может написать код для распознавания внешними камерами номеров автомобилей, который впоследствии возненавидят все автолюбители мегаполисов, а МАДИ и ГИБДД будут собирать со всех нас миллиардные штрафы для поддержания собственных иерархий для улучшения безопасности движения. Се ля ви...
Нам понадобятся следующие ингредиенты:
1. Быть другом Романа Андреева (в хорошем смысле слова) на Смартлабе, чтобы иметь доступ к его блогу.
2. Любая среда Питон (я использовал Jupyter Notebook Anaconda 3)
3. Установленный в Питоне пакет OpenCV — библиотека алгоритмов компьютерного зрения и обработки изображений с открытым кодом. Установка заняла несколько секунд командой $pip install opencv-python. Маленький лайфхак, про который постараюсь подробно рассказать в будущих топиках. Если у вас нет Питона, воспользуйтесь халявой на Google Colab. Там вам предоставят бесплатный Питон в браузере, уже установленные библиотеки, в том числе OpenCV, халявные вычисления на GPU для нейросетей и много других плюшек. Взамен потребуют, как обычно — всего лишь вашу душу:)
4. Набор шаблонов (графических изображений цифр, слов и знаков) для распознавания (ссылка). Папка называется RomanAndreev и содержит в себе две папки. Папка ImageSamples — в ней собраны шаблоны — png-файлы слов, цифр и знаков для распознавания, будем называть их токенами. Я заранее подготовил шаблоны, затратив на это время, сопоставимое с временем написание кода. Выглядит так:
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.
Папка Data. В этой папке собраны графические файлы с рекомендациями (примерно с конца сентября, исключая дни, когда Роман лакомился муксуном и нельмой в нефтедобывающих краях). И в нее вы будете копировать новые рекомендации (правая кнопка мыши на табличке — «Сохранить картинку как»), из которых мы будем извлекать информацию для каждого тикера.

Итак, исходные данные в сборе, переходим к решению.

Сама по себе задача поиска текста на изображениях не является тривиальной, особенно если нужно понять смысл текста (семантику). Но в нашем случае все оказывается гораздо проще. Сходу приведу кусок кода, который распознАет цифру 8(восемь) в файле рекомендации от 1 декабря 2020 года и отметит ее местоположение на изображении.

import cv2 as cv
import numpy as np

template = cv.imread('RomanAndreev\\ImageSamples\\8.png', 0) #читаем шаблоц цифры "8"
img_rgb = cv.imread('RomanAndreev\\Data\\2020-12-01.png') #Читаем изображение рекомендации от 1 декабря 2020 года
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY) #Делаем изображение черно-белым в оттенках серого
img_gray = cv.GaussianBlur(img_gray, (3, 3), 0) #Слегка размываем изображение с помощью фильтра Гаусса
w, h = template.shape[::-1] #Определяем ширину и высоту шаблона
res = cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED) #Проходим шаблоном по изображению в поисках совпадений
threshold = 0.8 #Задаем порог, при превышении которого шаблон считается найденным на изображении
loc = np.where( res >= threshold) #Таких мест на изображении может быть много, записываем их координаты в матрицу
#Рисуем на основном изображении рамочки, где мы нашли шаблон
for pt in zip(*loc[::-1]):
    cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 1)
cv.imwrite('res8.png',img_rgb) #Записываем результат в файл
После выполнения кода (CTRL+Enter) в файл res8.png будет выглядеть так:
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.

Так вот как камеры определяют номера автовладельцев-нарушителей(но нам бояться нечего — мы все ездим по правилам)!
Понятно, что распознавание не идеально, и как мы видим, за восьмерку были приняты заглавные буквы B-латинская и В-кириллица. От восьмерки еще отличить их можно, но между собой — никак...
Ничего страшного, вспомним классику. В х/ф «Особенности национальной рыбалки» между двумя персонажами состоялся такой диалог: — Весной ящик коньяка со сходен уронили. Когда день рождения начальника тыла праздновали. Так они в мути у военного пирса его отыскали и подняли. — А там глубоко? — Глубоко. — Достанут! — Очень глубоко. — Все равно достанут. Когда знают чего и сколько утопили — достанут!
Вот так и мы, в этой задаче знаем, чего и сколько искать. Для этого выделим по контурам названия инструментов (тикеров), ключевые слова «лонг» и «шорт», а также цифры от 0 до 9 и запятую, и сохраним их в качестве шаблонов — назовем их токенами. Они лежат в папке ImageSamples.
Принцип распознавания следующий: сначала мы находим изображение тикера (например 'Ri-') на основном изображении. Затем движемся вправо по горизонтали, чтобы найти токены «лонг» или «шорт», далее снова движемся вправо в поиске цифр и запятых, если очередная цифра расположена на расстоянии от предыдущей, значит в этом месте начинается новое число.
В коде, который представлен ниже основная функция StartRunning('2020-12-07'), принимает строку в виде ГГГГ-ММ-ДД, соответствующую дате рекомендации и имени png-файла изображения с таблицей 2020-12-07.png, который вы сохранили в папку Data. Фактически функция распознает то, что находится на изображении и сохраняет эту информацию уже в формате csv, в файле ГГГГ-ММ-ДД.csv. Затем читает этот файл в датафрейм Pandas и возвращает его для дальнейшей обработки.

Код с подробными комментариями:

#©2020 by Evgeny Shibaev. А пользуются теперь НУ ВООБЩЕ ВСЕ!

#Для установки OpenCV воспользуйтесь командой $ pip install opencv-python
#Код для распознавания на графическом изображении чисел, слов и знаков препинания по образцам. Для примера взяты
#изображения рекомендаций по различным финансовым инструментам из блога Романа Андреева на Смартлабе.
#В результате выполнения кода мы преобразуем картинку в дата фрейм, соответствующий содержанию рекомендации.
import cv2 as cv
import numpy as np
import pandas as pd
import csv
import os.path
from matplotlib import pyplot as plt

#Список инструментов (тикеров)
ticker_names = ['BR-','GD-','RI-','Si-','MICEX','Eu-','VTBR','GAZP','GMKN','LKOH','MTSS','ROSN','RTKM','SBER','SBERP',
                'CHMF','SNGS','SNGSP','FEES']

#tokens - список имен png-файлов, содержащих изображения символов и слов, которые нужно найти на изображении (без учета
#имен тикеров):['0','1','2','3','4','5','6','7','8','9','comma','long','short']. Файлы хранятся в папке \\ImageSamples\\
tokens = [str(digit) for digit in range(10)] + ['comma', 'long', 'short'] #Templates file names

#folder = папка в Питоне, содержащая папку Data с .png-файлами рекомендаций и папку ImageSamples с образцами цифр,
#знаков и слов для распознавания.
folder = 'RomanAndreev'

# locations это словарь, ключами которого будут имена токенов, а значениями список координат, где эти
# токены были выявлены на основном изображении
locations = {}

#Порог, при превышении которого образец считается найденным на основном изображении. С порогом можно "поиграть",
#если какой-то шаблон не идентифицтруется. Стандартно порог=0.8 В нашем случае эмпирически найдено 0.789.
#На десяти тестовых файлах не было ни одной ошибки при распознавании
threshold = 0.789


#Основная функция для запуска. Извлекает из изображения данные, сохраняет их в формате csv и возвращаетв датафрейм
# date - строка вида '2020-11-30', дата в формате ГГГГ-ММ-ДД. Соответствует имени файла изображения в папке Data
def StartRunning (date):
    #Файл куда вы сохранили картинку с рекомендациями по тикерам
    image_file = f'{folder}\\Data\\{date}.png'
    
    #Проверяем не забыли ли вы сохранить изображение
    if not os.path.exists(image_file):
        print(f'No such file: {image_file}')
        return False

    #Читаем .png файл из которого будем извлекать информацию
    img_rgb = cv.imread(image_file)
    
    #Делаем изображение черно-белым в оттенках серого - засеряем (наверное от слова "засеря")
    img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
    
    #Определяем координаты нужных нам цифр, знаков препинания и слов на изображении, т.е. заполняем словарь locations
    GetTokenLocations(img_gray)
    
    #По каждому тикеру записываем строку с рекомендациями в файл 'ГГГГ-ММ-ДД.csv' 
    file = f'{folder}\\Data\\{date}.csv'
    with open(file, mode='w', encoding='utf-8') as f:
        #Для записи информации в CSV файл создаем объект writer
        fw = csv.writer(f) #для просмотра в EXCEL добавьте delimiter = ';', lineterminator='\r'
        fw.writerow(['Date', 'Tiker', 'Posa', 'Open', 'Reverse', 'PL'])
        #По каждому тикеру записываем строку с рекомендациями в файл 'ГГГГ-ММ-ДД.csv' 
        for ticker in ticker_names:
            fw.writerow(GetTikerInfo (date, ticker, img_gray))

    #Для проверки, что все чики-пуки, зачтём данные из csv файла в датафрейм Пандас df     
    df = pd.read_csv(file, index_col = 'Tiker')  
#    print(df)
    return df

#Функция определяет координаты найденных токенов на изображении image и сохраняет их в locations
def GetTokenLocations (image):
    #Для каждого токена
    for token in tokens:
        #Читаем из файла образец изображения токена в template
        template = cv.imread(f'{folder}\\ImageSamples\\{token}.png',0)
        #Собственно "волшебство" распознавания: матрица, содержащая степень похожести образца с куском основного
        #изображения. Используем небольшое "Гауссово размытие" - изображение становится как будто вы забыли надеть очки
        res = cv.matchTemplate(cv.GaussianBlur(image, (3, 3), 0),template,cv.TM_CCOEFF_NORMED)
        #Сохраняем в словаре, для каждого токена координаты точек, где совпадение превышает заданный порог
        locations[token] = np.where(res >= threshold)

#Функция для определения рекомендации по тикеру, возвращает список типа: ['2020-11-30', 'MICEX', 'long', '2758', '3112']
def GetTikerInfo (date, tiker_name, img_gray):
    #Читаем в template изображение тикера из файла
    template = cv.imread(f'{folder}\\ImageSamples\\{tiker_name}.png',0)
    w, h = template.shape[::-1]
    res = cv.matchTemplate(cv.GaussianBlur(img_gray, (3, 3), 0),template,cv.TM_CCOEFF_NORMED)
    #Определяем координаты наилучших совпадений с образцом
    loc = np.where(res >= threshold)
    #Находим координату Y, чтобы значение было не только выше порога но и максимальным.
    #Таким образом мы привязали Y к строке, в которой расположена информация о тикере
    Y = loc[0][np.argmax(res[loc], axis = 0)]
    #Одному и тому же изображению могут соответствовать несколько соседних точек, особенно если шрифт жирный, 
    #мы впоследствии оставим только одну из них, но сейчас сделаем окрестность dY из ближайших точек
    dY = (Y, Y+1, Y-1)
    #token_pos - словарь, ключами которого являются токены, а значениями -  списки координат, соответствующих этим токенам
    #У одного токена может быть несколько координат, например для значения 48,88 у токена '4'и ',' - будет по одной 
    #координате, а токена '8' - три.
    token_pos = {}
    rightx, posa = 0, 0 #Инициализация переменных. Описание далее.
    #Для каждого токена определяем его позицию в окрестностях координаты Y (в строке) и оставляем только одну
    for token in tokens: #loop only for tokens        
        for pt in zip(*locations[token][::-1]):
            if pt[1] in dY:
                #Для координаты Х также создаем окрестность и проверяем, чтобы одному токену соответствовала только одна
                #координата Х
                dX = (pt[0], pt[0]+1, pt[0]-1)
                #Этим выражением исключаем "лишние"значения координат Х для одного токена
                if not (any(map (lambda key: key in dX, token_pos))):
                    #Если мы нашли изображения слов 'шорт' или 'лонг', то запоминаем позицию в posa, а также точку (rightx)
                    #от которой будем "искать" вправо собственно цену открытия позиции и ее реверса.
                    if token in ('short','long'):
                        posa = token
                        rightx = pt[0]
                    token_pos[pt[0]]= token
    #После окончания цикла инициализируем строку значением ticker и posa через пробел (она содержит позицию по тикеру)
    price_str = date + ' ' + tiker_name + ' ' + posa
    #indent - задает расстояние в пикселях между токенами (буквами или цифрами), в случае превышения которого 
    #мы считаем что, следующий найденный токен будет началом нового слова (или числа)
    indent = 30 #pixels
    oldx = rightx
    #Сортируем найденные в строке токены по возрастанию координаты Х, не включая те токены, которые находились левее
    #столбца "Позиция" на изображении. Т.к. слева могут попадаться цифры в наименовании тикера, например Ri-12.20
    for item in [i for i in sorted(token_pos.items()) if i[0] > rightx]:
        if item[0] > oldx + indent:
            price_str = price_str + ' ' + item[1]
        else:
            price_str = price_str + item[1]
        oldx = item[0]
    #После окончания цикла строка price_str будет примерно такой "short 47comma53 49comma1". Заменим в ней 'comma' 
    #на '.', затем разделяем на пробелы получим список с рекомендацией типа:['short', '47.53', '49.1']
    return price_str.replace('comma', '.').split()

#Если вы нашли ошибку при распознавании, например пропущена какая-то цифра (что было редко) или цифра распозналась 
#неправильно (такого не было), то есть два способа исправить. Первый простой - откройте блокнотом csv-файл в папке Data и 
#исправьте вручную. Второй - более сложный, но позволит избежать подобной ошибки в будущем: откройте основное изображение
# графическим редактором, выделите на основном изображении ту цифру, которая была пропущена, например 8, обрежьте
#изображение до размеров остальных шаблонов и сохраните в файл под именем '8.png' в папку ImageSamples поверх
#старого шаблона. Затем перезапустите основную функцию и проверьте результат распознавания

res=StartRunning('2020-12-07')
res

Для чего я сделал промежуточное сохранение в csv формат? Очень редко, но встречаются ошибки при распознавании, возможно из-за того, что порог распознавания treshold оказался чуть выше найденного значения и токен не попадет в список (будет просто пропущен). В этом случае откройте файл ГГГГ-ММ-ДД.csv обычным блокнотом, вставьте пропущенную цифру и сохраните изменения. Вторая причина — все мы люди, и Роман может ошибиться (нет, не в направлении движения рынка) — просто по невнимательности. В этом случае, любая не стыковка сразу будет видна на графиках. Я приведу пример ошибочно указанного направления для Eu-12.20 в конце статьи.

После того, как мы извлекли нужную нам информацию из изображения, ее нужно представить графически. Данные для отображения графиков мы берем с сайта Финама. Сам код детально описан в предыдущем топике. В него лишь добавлены тикеры 'Si-','RI-','Eu-','GD-' и 'BR-' и их коды для декабрьских контрактов (нефть — январь). При переходе на новые контракты исправьте коды тикеров, зайдя на сайт Финама. Имена тикеров должны соответствовать именам в переменной ticker_names.

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={'Si-':502420,'RI-':502418,'Eu-':893255,'GD-':924737,'BR-':926134,'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 #91 24 #можно не задавать. Это рынок, на котором торгуется бумага. Для акций работает с любой цифрой. Другие рынки не проверял.
    # Текущий момент времени
    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()

#Преобразует строку вида "2020-11-02" в дату (формат datetime)
def ToDateYYYYMMDD (date_yyyy_mm_dd):
    return datetime.strptime(date_yyyy_mm_dd, '%Y-%m-%d').date()

SBER = GetCandles ('SBER', "30min", 10)
SBER
Вы можете, для примера, запустить этот код независимо, в результате получите датафрейм, содержащий 30 минутные свечи акций Сбербанка за последние 10 дней. Этот код мы используем для отображения графиков.
И собственно, код для визуализации. Переменная-словарь tickers содержит названия тикеров для отображения. Если вы не анализируете (с помощью Романа) какие-то тикеры — закомментируйте их — остальные графики отобразятся быстрее. Визуализация запускается функцией start_function() — в конце кода. В начале этой функции определяются переменные, которые можно менять для удобства восприятия. Сейчас они настроены на отображение часового графика за 45 календарных дней и с рекомендациями за последние 30 торговых сессий.
import time
import os
import matplotlib.pyplot as plt

#Список инструментов, которые анализирует Роман. Тикер - токен: тикер это торгуемый актив, а токен - его привычное название.
# Ключ в словаре tickers ДОЛЖЕН СТРОГО СООТВЕТСТВОВАТЬ ключу в словаре symbols (в модуле загрузки котировок с Финама)
tickers = {'RI-' : 'RI-12.20',
           'Si-' : 'Si-12.20',
           'BR-' : 'BR-1.21',
           'GD-' : 'GD-12.20',
           'MICEX':'Индекс ММВБ',
           'Eu-' : 'Eu-12.20',
           'VTBR' : 'ВТБ',
           'GAZP' : 'Газпром',
           'GMKN' : 'ГМК Норильский никель',
           'LKOH' : 'Лукойл'
           'MTSS' : 'MTC',
           'ROSN': 'Роснефть',
           'RTKM': 'Ростелеком',
            'SBER': 'Сбербанк, об',
            'SBERP': 'Сбербанк, пр',
            'CHMF' : 'Северсталь',
           'SNGS' : 'Сургутнефтегаз, об',
           'SNGSP': 'Сургутнефтегаз, пр',
           'FEES' : 'ФСК ЕЭС'
          }

#################################### Помощники для построения графиков ##########################################
#Определяет начальную и конечную позицию Х (по индексу свечей) для заданной даты. Пригодится при отрисовке ценовых уровней
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([dt.strftime("%d %b") for dt in dates])
    fig.set_xticks(xlabel)
    return dates, xlabel

#Рисует основной график
def draw_candles(ticker, 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('График ' + tickers[ticker])
    #Рисуем шкалу с датами
    PlotDatesX (fig, candles)

#Если в числе присутствует десятичная точка и после нее нет значащих цифр, делаем из числа целое - "чисто для красоты"
def PointOff(valfloat):
    if valfloat % 1 == 0:
        return int(valfloat)
    return valfloat

#Рисует уровни открытой позиций и уровни переворота позиции на заданную дату, для свечек (candles) тикера. Где Posa -
# текущая позиция 'long' или 'short'на начало дня, price - уровень открытия позиции, reverse - уровень переворота позиции
#Также, отображает зеленым и розовым цветами (с заливкой) уровни предполагаемой прибыли или убытка относительно
#текущей позиции если она будет закрыта (реверсирована)
def draw_levels (datetime_date, candles, posa, price, reverse):
    DX = DateX(datetime_date, candles)
    x = len(candles) #Координата Х правого края графика
    last_price = PointOff(candles.iloc[-1]['<CLOSE>']) #цена последней сделки
    posanum = -1 if posa == 'long' else 1
    equity_color = 'lightgreen' if posanum * (price - reverse) > 0 else 'lightcoral'
    plt.fill([DX[0], DX[0], DX[1], DX[1]], [price, reverse, reverse, price], alpha = 0.2, color = equity_color)
    plt.text(x, last_price, str(last_price), color = 'white', verticalalignment='center', bbox={'facecolor': 'slategray', 'pad': 2})
    posa_color = 'seagreen' if posa == 'long' else 'firebrick'
    plt.plot(DX, [price, price], color = posa_color, linewidth = 3)
    reverse_color = 'red' if posa == 'long' else 'seagreen'
    plt.plot(DX, [reverse, reverse], color = reverse_color, linewidth = 0.5)
    plt.text(DX[0], price, f'{price}', color = 'white', verticalalignment='center', bbox={'facecolor': posa_color, 'pad': 2})
    v_aligment = 'top' if posa == 'long' else 'bottom'
    plt.text(DX[0], reverse, f'{reverse}', color = reverse_color, verticalalignment=v_aligment)
       
def start_function():
    folder = 'RomanAndreev/Data/'
    days_past = 30 #за какое количество торговых сессий от сегодняшней показывать позицию
    days_for_chart = 45 #за какое количество календарных дней строить общий график
    time_frame = 'hour' #какой тайм-фрейм использовать для графика
    daily_df = {} #Словарь, ключами которого будут даты рекомендаций, а значениями датафреймы с самими рекомендациями для каждого тикера на заданную дату
    for file in [f for f in os.listdir('RomanAndreev/Data/') if '.csv' in f][-days_past:]:
        filedate = file.split('.')[0]
        daily_df[filedate] = pd.read_csv(f'RomanAndreev/Data/{file}', index_col = 'Tiker')
        print(file, 'load')

    #Проход по всем тикерам в списке рекомендаций
    print('Please, wait...')
    for ticker in tickers:
        last_levels = [] #в этой переменной после окончания цикла будут уровни текущего дня, которые мы подпишем на графике
        last_advice = '' #а в этой переменной рекомендации на текущий день
        #Читаем в датафрейм candles свечки с сайта Финама
        candles = GetCandles (ticker, time_frame, days_for_chart)
        time.sleep(0.5) #делаем небольшую задержку в запросах к серверу котировок, чтобы нас Финам не забанил
        #Рисуем свечи
        draw_candles(ticker, candles)
        for current_date in daily_df:
            df = daily_df[current_date]
            draw_levels(ToDateYYYYMMDD(current_date), candles, df.loc[ticker]['Posa'], PointOff(df.loc[ticker]['Open']),PointOff(df.loc[ticker]['Reverse']))
    return daily_df    

start_function()
True
Если вы все сделали правильно то в результате получите примерно следующие графики:
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.
Открытая на утро каждого дня позиция отображается как цена на красном фоне для «шорта», на темно-зеленом фоне — для «лонга». Уровни для переворота позы обозначаются без заливки — зеленым для переворота в «лонг», красным — в «шорт». Розовые зоны обозначают предполагаемый убыток, если в этот день позиция будет закрыта (перевернута) на уровне реверса. Светло-зеленые зоны  — это предполагаемая прибыль, относительно текущей позиции, в случае если позиция будет закрыта  на уровне реверса.
Про ошибку… На графике ниже по Eu-12.20 видна ошибка, возникшая в рекомендации:
Визуализация рекомендаций Романа Андреева на Python. Часть 2. Компьютерное зрение.
Очевидно, что Роман имел ввиду что позиция по 92350 от 6 ноября должна была иметь направление «шорт», т.к. предыдущая позиция была «лонгом».
А что подумали вы?

Подведем итог и повторим алгоритм действий:
1. В Смартлабе заходите в блог Романа Андреева, копируете табличку с рекомендациями и сохраняете изображение в папке RomanAndreev\Data\ под именем даты рекомендации, например 2020-12-07.png
2. Запускаете распознавание StartRunning('2020-12-07') в первой ячейке, которая сохранит рекомендацию в формате csv.
3. Проверяете экспорт с Финама — запуском второй ячейки, если все ок, то
4. Визуализация — запуск третьей ячейки.
В принципе весь код можно объединить.  Не забудьте про исходные данные RomanAndreev

Буду рад, если эта статья поможет вам в заработке. Лично я, пока потерял во времени: 4 часа — написание кода, 1 час — подготовка шаблонов для распознавания, 2 часа на эту статью. Умножаем на 1700 руб/час = порядка 12тр. Понятно, что время бесценно. Но если кому-то придет в голову отблагодарить автора фиатом, я не буду против донатов. Мошна намбэр 410012324117195 на Яндекс Юмани. Собранный миллион разделим по-честному — 45% Роману, 45% -мне, 10% Остап Ибрагимовичу Тимофею. Если оставите мыло, все обновления будут у вас на мыле. Ну а так — пользуемся бесплатно и делаем деньги.

ЗЫ. Забыл самое главное — СПАСИБО РОМАНУ АНДРЕЕВУ ЗА ЕГО ТРУД!

Даже не хочу упоминать, что у меня есть канал на ютьюбе (чтобы не забили камнями)








★83
76 комментариев
А, что делать будет распознаватель когда Роман, по ошибке, вместо шорта напишет лонг? :) ну или цифры в запарке перепутает?
avatar
Семён Семёныч, было такое: "… На графике ниже по Eu-12.20 видна ошибка, возникшая в рекомендации:..." С 6-го аж по 11 ноября висит лонг вместо шорта… В этом случае включаем голову и в csv файлах меняем вручную «лонг» на «шорт» или звоним Роману  — делаем пальцы врастопырку — наезжаем на него — полстраны из-за тебя лонг по евре против рубля держат — имей совесть — переворачивайся)))
avatar
Евгений Шибаев, Я двумя руками за Ваши старания! ОЧень красиво получилось! Надеюсь поможет даже кому-то заработать…
avatar
Спасибо. С комментами — самый сок.
Константин Доронин, я старался
avatar
Евгений Шибаев, вопрос, а где вывод? В плюс торгует или нет?
 Почему уелыкий гуру не выйдет на ЛЧИ и не докажет умение торговать?))
   Ах да был два года назад что то вроде -20% показал.
  Больше не ногой,)))
avatar
Дед Панас, я по Роману выводов здесь не делаю, т.к. сам не торгую по его системе. Просто этим кодом постарался облегчить жизнь другим.
avatar
Евгений Шибаев, бу га га
avatar
Евгений Шибаев, если ты не знаешь результата, то загугли, что такое


    сизифов труд
avatar
Дед Панас, тема топика не звучит так: В плюс ли торгует Роман Андреев? Если вы не понимаете что такое «визуализация» (она есть тема топика) — то Гугл не обязателен — достаточно Яндекса.
avatar
Евгений Шибаев, сизифов труд ))и не более, удачи вам и как программисту уважуха, потому что коллеги
avatar
Дед Панас, взаимно, удачи!
avatar
чтото мне кажется что логика уровней самого Романа проще чем ваша прога :) но все равно круто
Иван Файртрейдов, согласен, логика проста(правда в деталях сами знаете кто, не к ночи будет... ), но если бы Роман выкладывал рекомендации сразу в csv, я бы не тратил свое время на написание кода…
avatar
Ошибки ))) да они есть и часто раньше были, но Роман молодец — он их комментировал… помню с появления его поста тысячи просмотром
И комментов было… тут же… эээ я просто года три не заходил на рынок вообще… и тут такое беда… автору статьи БРАВО!!!
avatar
Такое за несколько часов написать… Очень круто. Завидую Вашему таланту:)
avatar
Врач-бондиатОр, ну немного слукавил, экспорт с Финама (за основу взял код Albusа) и кое-что в визуализации были написаны в предыдущем топике. А так в целом да. Вот на комменты времени много ушло…
avatar
Пост наглядно показывает важность человека, способного сформулировать задачи для программиста.
avatar
$100, да, я так и подумал про себя, т.е. того самого человека, способного сформулировать задачу себе — программисту...))
avatar

Мощно).

Ещё сюда парсер просится, который картинки сам парсит из всех постов.

 

У меня б больше времени ушло, наверно).

avatar
Replikant_mih, да, в плане парсера можно все довести до полного автомата, просто использовать параметры авторизации в запросах к Тимофею. Ну тогда люди вообще к Роману не будут заходить на «огонек» и общаться между собой в его блоге. А там можно общаться без масок и перчаток…
avatar
Евгений Шибаев, Ну эт да, общение это дело хорошее. Правда именно туда я никогда не заходил. Вернее один раз зашел посмотреть откуда там всегда столько сообщений, увидел, что там только одних приветов дофига, закрыл).
avatar
вывод-то какой? 
avatar
Ilya, простой рома торгует в плюс
avatar
technic, есть выписки?
avatar
GrayFox, посмотрите внимательно картинки
avatar
Ilya, надеюсь вывод каждый сам для себя сделает.
avatar
Как то вы неправильно час работы считаете, нужно брать ставку сеньор девелопер из силиконовой долины в долларах конечно))
avatar
technic, спасибо конечно за оценку…
avatar
 

Вот только не ясно зачем делать blur, если он заведомо ухудшает качество и почему нет ресайза. Получается шаблон мы ищем определенного размера, так?
avatar
Kot_Begemot, да, шаблон определенного размера. Если Роман напишет рекомендацию  шрифтом другого размера, то распознавание превратится в сплошную ошибку. Я попрошу вас коллега, если есть такая возможность, улучшить собственно распознавание, в.т. числе применив масштабирование и объяснить детали. Дело в том, что я познакомился с OpenCv только 3 дня назад и не знаю еще всех возможностей этой обширной библиотеки. Покрутил-повертел — в итоге добился того, что на данных с 25 сентября  (глубже не было времени проверять), все распознается без ошибок. Примеры кода можно оставить в качестве ссылки на Гуглколаб. Буду весьма признателен. Да и сам код собран «на коленке», уже нашел места где можно кое-что улучшить, будет время — поправлю.
avatar
Евгений Шибаев, вы так меня спрашиваете, как буд-то я профессионально этим занимался. А я даже Питон не знаю)))

Но особо там придумывать нечего — масштабируете спектр картинки в кратное число раз (промежуточные масштабы тоже сработают кое-как), и гуляете по своему изображению с набором спектральных шаблонов. 

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

Там всё ой как не просто, поэтому я и спросил вас)
avatar
Kot_Begemot, понял, коллега, спасибо за развернутый ответ, как будет время обязательно попробую «отмасштабироваться», принцип теперь понятен.
avatar
Всё это здорово, одно НО, не заработать этой табличкой, и не надо прикрываться тем, что типа надо правильно входить. 1700руб. за час работы? Похвально, — гы… автор, да вы лучше код пишите — больше заработаете, нафиг вам этот трейдинг
avatar
Igor_engineer, а у меня и программированием и трейдингом получается зарабатывать. Трейдингом сильно больше — уже почти 30 лет.
avatar
Евгений Шибаев, 
Трейдингом сильно больше — уже почти 30 лет.
… Да-да, ну а я в 91-ом фичу кодил на квик бейсике, которая шортила Pan American World Airways
avatar
Андрей Андреичъ, «не судите опрометчиво, говорит Евангелие...» И этот топик не имеет цели проверить доходность. Его задача облегчить подписчикам Романа жизнь, тем кто действительно торгует по его рекомендациям. Я  сам не проверял доходность рекомендаций, и не торгую по ним, но если навскидку то у Романа неплохая трендовая система, и естественно ее распиливает на боковиках, посмотрите графики на SNGS об. А так с сентября на длинных трендах -заработок.
avatar
Молодец! :-)
Тоже что ли заняться Питоном, а то отстал от жизни давно…
avatar
Евгений Петров, никогда не поздно начинать…
avatar
Евгений Шибаев, А что перспективнее Питон или Джава? Я вот Джаву пытаюсь изучать(очень мало времени). Вот еще анlроид разработку начал — просто пытаюсь вАйти в Айти)) С андроид-разработки мне кажется проще, ибо джава там так поскольку-постольку, начинать проще с простого.

avatar
Igor_engineer, все зависит от задач, я вообще на Лиспе профессионально начинал писать и до сих пор продолжаю. После этого Питон не составило труда освоить. В питоне огромное кол-во библиотек для анализа данных и машинного обучения, простой, гибкий. Джава кроссплатформенная тоже весьма гибкая и быстрая одним абзацем всего не опишешь. Лучше всегда плясать от задачи, а там уже выбирать
avatar
Евгений Шибаев, Просто пашу на работе инженеришкой за жалкие 50 тыщ, хочу вАйти в Айти)) Знаю, что у вас 50 тыщ — это удел новичка или чела как на стройке разнорабочий. Вот и стал изучать джаву на джавараш, но к сожалению мало времени, и решил освоить андроид-разработку, так как это джава лайт по-сути. Раньше делал сайты на пыхе, но очень пожалел, что пошел инженером после универа((
avatar
Igor_engineer, не огорчайтесь, все в ваших руках, если есть желание. Опыт разработчика приходит со временем. Если у вас за плечами универ — то это круто. У меня вообще военное училище — и я инженер, но подготовка была крутая, могу стрелять не хуже киллера, метать холодное оружие, обладаю навыками выживания в экстремальных условиях и взрывотехникой. Ну и конечно, программирование, управление активами (этому в училище не обучали) и еще много чего. На то он и инженер, чтобы уметь все.
avatar
Евгений Шибаев, чертежик кинете метательного ножа? Давно хочу такой выпилить :)
avatar
Врач-бондиатОр, да обычный штык-нож возьмите или кортик)
avatar
Евгений Шибаев, а разве лезвие не должно быть тяжелее рукоятки для метания?
avatar
Врач-бондиатОр, в идеале, но всегда под рукой что есть — то есть.
avatar
Igor_engineer, андроид не так прост. Нет единного станрдарта. Под каждый гаджет пили свой код. Да и на kotlin сейчас многие посматривают в плане мобильной разработки. Учи лучше джаву.
avatar
Андрей Андреичъ, Абсолютно верно. Проверял его «систему» — сливает, увы… Скажу больше- лет 5 назад в его группе было намноооого больше народу. А сейчас с гулькин нос — толи денег у людей меньше стало, толи все поняли, что его система — это реклама его платных услуг.
avatar
Красава, автоматизация — наше всё

avatar
Ну, так суть системы выкристаллизовалась? В виде правил.
avatar
svgr, суть системы выходит за рамки этого топика
avatar
Евгений Шибаев, разве это я спросил? Вы для себя то её выяснили?
avatar
svgr, я сам не торгую по системе Романа, поэтому и не выяснял ее алгоритм
avatar
Добрый день, Евгений! В прошлом посте Вы говорили, что примерно к 14 ноября закончите работы по коннекторам, которые анонсировали в середине октября. Удалось?
avatar
Добрый вечер, Сергей! По Jatotrader — как гласит закон Хеопса: «Ничто и никогда не делается в срок и в пределах сметы» — я работаю над коннекторами, есть ошибки при тестировании, но постараюсь Бинанс выдать в этом году, а МТ5 прям в начале следующего… Сорри, что не уложился в сроки обещанного…
avatar
Евгений Шибаев, Понял. А там ещё речь шла о коннекторе к Питону (получение любых данных и индикаторов Джато в Питон и управление заявками из Питона в Джато)…
avatar
Сергей Давыдов, а, ну это точно в этом году!
avatar
Евгений Шибаев, Евгений, добрый день! Удаётся в этом году?
avatar
Сергей Давыдов, немного не успеваю, сорри). Приходилось отвлекаться на торговлю. Надеюсь на новогодних каникулах «поставить точку».
avatar
Евгений Шибаев, Евгений, поздравляю с наступившим НГ! Желаю эффективности во всех начинаниях! И поставлена ли «точка» в связи окончанием каникул?
А у меня в третьей ячейке ошибка
daily_df[filedate] = pd.read_csv(f'RomanAndreev/Data/{file}', index_col = 'Tiker')
NameError: name 'pd' is not defined

Кстати в списке инструментов tickers = {}  после 'LKOH': 'Лукойл'   нет запятой…
Сергей Брониславович, Вы по-очереди выполните все ячейки Ctrl+Enter. Т.к. pd определяется во второй: import pandas as pd
После Лукойла должна быть запятая. Спасибо за замечания.
avatar
Евгений Шибаев, Запустил. Так и делал — по очереди 1,2 и 3 модули но 3 модуль не видел pd хотя я видел его во втором блоке и только после отдельного определения в pd 3 модуле он запустился Почему не знаю. А вот вместе в одном файле все три модуля без проблем. Правда пришлось добавить plt.show()   в конце  start_function() Без этого графики не выводились
Мне понравилось, на Python никогда не пытался ничего делать Здорово.
Может попробую :)
С точки зрения практического использования то конечно реверсивные системы спорное дело...  А вот уровни по второй картинке и таблицы в комментариях от Андреева, да еще с выводом на графики Quik посредством обработки подготовленных на Python файлов индикатором Lua было бы наглядно.  Я Квик поставил только по просьбе сына присмотреть за его сделками. Последний раз видел это творение лет «дцать» назад — ну просто слов нет что за архаизм! И где наши самые лучшие в мире программисты?!
Удачи, спасибо за коды.
Евгений Шибаев, я их запускал в IDLE Python в трех окнах по очереди.
Евгений, поясните пожалуйста выражение  w, h = template.shape[::-1]  или ссылку Особенно по [::-1] 
Сергей Брониславович, если выполнить вот этот код, мы создаем матрицу a 3х4. А выражением [::-1] мы реверсируем строки этой матрицы. Далее определяем размерность реверсированной матрицы

import numpy as np
a = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(a)
print(a[::-1])
print(a.shape[::-1])

>
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[ 9 10 11 12]
[ 5 6 7 8]
[ 1 2 3 4]]
(4, 3)
А в нашем случае просто определяем ширину и высоту шаблона template в пикселях.
avatar
Евгений Шибаев, Спасибо, разобрался Чтобы понять пригодно ли это для меня в практическом смысле попробовал другие варианты распознавания таблиц Качество слабовато. Попробовал метод cv2.findContours() с выделением ячеек таблиц с последующей обработкой pytesseract.image_to_string() но вот этот фрагмент кода выдает ошибку
coordinates.sort() #sort by x
coordinates.sort(key = sort2) # sort by y
for coord in coordinates:
    x,y,w,h = coord
    print(x,y,w,h)
    if y>prev_y+5: #new row if y is changed
        recognized_table.append(row)
        row = []
    crop_img = image[y:y+h, x:x+w]
в последней строке не принимает координаты «string indices must be integers»
Не могу понять в чем дело

   

если б к этим людям подключить динамо машину… )))

avatar
Ого, быстро пишешь! Я недавно начал питон изучать. Чтобы небольшие перестановки и вывод таблицы на пандас сделать столько же врени трачу. На такой код еще годик ущел бы ))
avatar
Александр Элс, со временем опыт придет
avatar
Евгений Шибаев, спасибо за поддержку :)
avatar

Приветствую, Евгений.
В словаре tickers пропущена запятая в строке с ключом LKOH.

… а еще много странностей, функции возвращают данные, но при вызове эти данные не принимаются, например, PlotDatesX.

… или вот не пойму в юпитере общая область видимости? в draw_candles вызывается PlotDatesX, где с переданной fig производятся манипуляции, но изменения не возвращаются функцией!

В питоне вроде бы должно быть так:

def foo(a):
    a = 3  # локальное изменение

if __name__ == '__main__':
    a = 1
    foo(a)
    print(a) # в результате все равно 1. манипуляции в foo не оказывают никакого влияния, ведь изменения не были возвращены и приняты.

В личку написать карма не позволяет, поэтому сюда.

avatar
Ronin From Moscow, в олд скул было так: если функция возвращает что-то — это функция, если не возвращает — что-то поделала и завершилась — это процедура. Все зависит от того, как вы определяете переменные. Если глобальные, то их видимость должна быть везде — как вне, так и внутри определений функций. А вообще давайте начнем с того — какая у вас среда Питона?
avatar
Евгений Шибаев, так вот откуда у 1С эти принципы…
avatar
>> В словаре tickers пропущена запятая в строке с ключом LKOH.
Евгений Шибаев, хороший способ отловить ошибки — собрать проект из кода в статье.

tickers = {'RI-' : 'RI-12.20',
           ...
           'LKOH' : 'Лукойл'
           'MTSS' : 'MTC',
           ...
          }
avatar

теги блога Евгений Шибаев

....все тэги



UPDONW
Новый дизайн