Fxとかのチャートからレートの時刻歴をCSV形式で保存するソフト

チャートからその数値を取得するプログラムを作ってみました。
チャートとして利用しているのはLINE FXですが、おそらくパラメータを変更させればほかのチャートも読み込むことができるのではないかと思います。よろしければ参考にしてください。
またソース自体は一番下、利用している言語はPythonになります。
必要なモジュールは
・pynput:マウスのクリックなどを判定するために利用
・OpenCV:画像処理に利用
・PyOCR:簡単に画像認識をすることができる便利なモジュール
・PIL:画像の読み込みとPyOCRに利用
・PyAutoGUI:画面上のクリックした座標を取得するために利用(正直OpenCVでも利用可能)
・Datetime:時間の足し引きなどに利用
・numpy:数値計算を行う際には必須のモジュールで、画像のような配列の計算も自分で関数を作成するより高速に動作する便利なやつ
となっております。

プログラムの流れは以下の通りです。
1,まずはチャートが写っている画面のうちグラフの部分のみを切り抜きます。そのほかの余計な部分が含まれてしまうと計算する際に非常に面倒ですので、棒グラフが写っている部分のみをクリックしてください。

2、一旦選択した領域を保存します。また基本的に左上から右下に向かって範囲を選択するような構造にしたかったので、誤ってクリックしてしまった場合などはそれ以上先に進まないようにする処理をしています。

3,ローカルの座標系を設定します。PC画面全体の座標系を使うと画面左上が原点=(0,0)になるのですが、切り抜いた画像を元に解析を行う方がスマートに計算ができます。ということで下の図のように新しい座標系を設定します。横=y方向の画素数はw,縦=y方向の画素数はhとしています。

4,(x,0)の点からy方向に1つ1つの画素数を取得します。画素はそのセルの赤(R)、青(B)、緑(G)の明るさの割合を示し、例えば黒の場合は(R,B,G)=(0,0,0)、赤の場合は(R,G,B)=(255,0,0)となります。
ラインFXのチャートの場合は青色の部分が(248,141,23)、赤色の部分が(59,49,232)となっています。

ここで同じ、もしくは似ている色を判断するにはどのようにすればよいでしょうか?これを調べるためには2つの色をベクトルとしてそのノルム=距離を計算すればいいのです。
下の図のようにRBGを軸として2つの色を選びます。左のように紫と緑の場合二つの色の距離は大きくなり、緑と黄緑のように似たような色の場合は距離が近くなります。

このことを利用して二つの色が似ているということをBGR空間上での2点間の距離に変換して判断しています。このようにすることでチャートの青や赤の色に似ているセルを探してその座標を得ることができます。
5,4と同様に下側から画素値の距離を計算し、BGR空間上で一定の距離以下のセルを検索します。

この時点でチャートの画面上の座標を取得することができます。しかしこのままではただの座標ですので、x軸を時間、y軸をレートに変換していきます。
6,画面上のわかっている時刻、例えばチャートの縦軸が書かれている部分2点を選択しその時間の間隔と画面上のピクセル数から1ピクセル当たりの時間を計算します。

本来ならば時刻も自動的に収集できればいいのですが、そこまでは実装できませんでした。というわけで時刻に関してはinput関数を利用して手動で入力します。

7,y軸も6と同様の手法を利用して座標からレートに変換させます。

8,最後にx軸を利用して計算した時刻の行列とy軸を利用して計算下レートの行列を結合させます。最終的にできた配列をCSVとして出力してあげれば画面中のレートをCSVの形で取得することができます。

とりあえずプロトタイプとして作成しましたが、やはり繰り返し計算を多用してしまっているせいで処理が非常に重たいという問題があります。まあほっておくだけでできるので問題ないといえば問題ない気もするのですが…
もっと楽な方法があったら教えてくださーい。

以下非常に見づらいコード




from pynput import mouse
import cv2
from PIL import ImageGrab,Image
import numpy as np
import pyautogui as pag
import pyocr
import os
import datetime

path='C:\Program Files\Tesseract-OCR'
os.environ['PATH'] = os.environ['PATH'] + path
pyocr.tesseract.TESSERACT_CMD = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

class func:
    def __init__(self, *args, **kwargs):
        self.th=120

        #切り抜きを行う範囲  (x1,y1)と(x2,y2)を対角とする四角形を指定する
        self.x1=0
        self.x2=0
        self.y1=0
        self.y2=0

        self.click=0
        self.num=1
        self.pic_name=''
        #pyocrの設定 
        self.tools = pyocr.get_available_tools()
        self.tool = self.tools[0]

        #チャートの色 
        self.color1=np.array([248,141,23])
        self.color2=np.array([59,49,232])

        #解析用の変数 
        self.value_hi=np.array([0,0])
        self.value_low=np.array([0,0])
        self.value_hi_trim=np.array([0,0])
        self.value_low_trim=np.array([0,0])
            #上記はそれぞれチャートの高値と低値を表している 
        self.color_th=50    #画素値のバラツキ範囲を示す 
        self.value_variation=1#前後の
        self.value_var_th=2#バラツキの大きさの指定

        #時間設定用 
        self.x_start_time=0
        self.y_end_time=0
        self.time_start=None
        self.time_end=None

        self.value_hi_output=np.array([0,0,0,0,0])
        self.value_low_output=np.array([0,0,0,0,0])

        self.y_upper_rate=0
        self.y_lower_rate=0
        self.rate_upper=None
        self.rate_lower=None

        
    def analysis(self):
        #画素値の習得 
        img=cv2.imread(self.pic_name)
        h,w,dim=img.shape[:3]
        print(h,w)

        for x in range(int(w)):
            for y in range(int(h)):
                dist1=np.linalg.norm(self.color1-img[y,x])
                dist2=np.linalg.norm(self.color2-img[y,x])
                print(dist1,dist2)
                if dist1<self.color_th or dist2<self.color_th:
                    
                    val=np.array([x+self.x1,y+self.y1])

                    self.value_hi=np.vstack((self.value_hi,val))

                    break

        for x in range(int(w)):
            for y in range(int(h)):
                dist1=np.linalg.norm(self.color1-img[h-y-1,x])
                dist2=np.linalg.norm(self.color2-img[h-1-y,x])
                if dist1<self.color_th or dist2<self.color_th:
                    
                    val=np.array([x+self.x1,h-1-y+self.y1])

                    self.value_low=np.vstack((self.value_low,val))

                    break
       
        row=self.value_hi.shape[0]
        for i in range(self.value_variation ,row-self.value_variation):
            print(self.value_hi[i])
            if np.abs(self.value_hi[i+self.value_variation,1]-self.value_hi[i,1])+np.abs(self.value_hi[i-self.value_variation,1]-self.value_hi[i,1])<self.value_var_th:

                self.value_hi_trim=np.vstack((self.value_hi_trim,self.value_hi[i]))

        row=self.value_low.shape[0]
        for i in range(self.value_variation ,row-self.value_variation):
            print(self.value_low[i])
            if np.abs(self.value_low[i+self.value_variation,1]-self.value_low[i,1])+np.abs(self.value_low[i-self.value_variation,1]-self.value_low[i,1])<self.value_var_th:

                self.value_low_trim=np.vstack((self.value_low_trim,self.value_low[i]))


        self.click=3

    def save_pic(self):
        if self.x1<self.x2 and self.y1<self.y2:
            cutImage=ImageGrab.grab(bbox=(self.x1,self.y1,self.x2,self.y2)).crop()
            cutImage.save(self.pic_name)
            #print ('画像を保存しました_'+self.pic_name)
            return 1
        else:
            print('座標がおかしいです')
            self.click=0
            self.num-=1
            return -1

    def output(self):
        #画像の読み込み 
        img=cv2.imread(self.pic_name,0)
        #画像の2値化 
        img_th = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,13,2)
        img_th = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,13,2)
        img_th = img
        #二値化した画像の保存 
        cv2.imwrite(self.pic_name_th,img_th)
        #
        img_th = Image.open(self.pic_name_th)
        builder = pyocr.builders.TextBuilder(tesseract_layout=6)
        text = self.tool.image_to_string(img_th, lang="jpn", builder=builder)

        print(text)

    def input_start_time(self):
        x,y=pag.position()
        self.x_start_time=x
        #日付と時間の入力 
        while 1:
            try:
                print("数値を入力してください\n年")
                y=2022
                print("月")
                m=input()
                print("日")
                d=input()
                print("時")
                h=input()
                print("分")
                mi=input()
        
                self.time_start=datetime.datetime(int(y), int(m), int(d), int(h), int(mi))
                break
            except:
                print('数値に異常がありました')


    def input_end_time(self):
        x,y=pag.position()
        self.x_end_time=x
         #日付と時間の入力 
        print("数値を入力してください\n年")
        y=2022
        print("月")
        m=input()
        print("日")
        d=input()
        print("時")
        h=input()
        print("分")
        mi=input()
        
        self.time_end=datetime.datetime(int(y), int(m), int(d), int(h), int(mi))


    def  convert_x2time(self):
        dt=self.time_end-self.time_start
        dt=dt.days*24*3600+dt.seconds
        dt_pix=int(dt/(self.x_end_time-self.x_start_time))
        
        row=self.value_hi_trim.shape[0]

        for i in range(1,row):
           print(self.value_hi_trim[i,0],self.x_start_time,dt_pix)
           dt=int(( self.value_hi_trim[i,0]-self.x_start_time)*dt_pix)
           dt_=self.time_start+datetime.timedelta(seconds=dt)
           yr=dt_.year
           mon=dt_.month
           day=dt_.day
           h=dt_.hour
           m=dt_.minute
           self.value_hi_output=np.vstack((self.value_hi_output,np.array([yr,mon,day,h,m])))
        
        row=self.value_low_trim.shape[0]

        for i in range(1,row):
           print(self.value_low_trim[i,0],self.x_start_time,dt_pix)
           dt=int(( self.value_low_trim[i,0]-self.x_start_time)*dt_pix)
           dt_=self.time_start+datetime.timedelta(seconds=dt)
           yr=dt_.year
           mon=dt_.month
           day=dt_.day
           h=dt_.hour
           m=dt_.minute
           self.value_low_output=np.vstack((self.value_low_output,np.array([yr,mon,day,h,m])))
        

                #レート設定 


    def input_upper_rate(self):
        x,y=pag.position()
        self.y_upper_rate=y
        print("レートを入力してください")
        self.rate_upper=input()
        self.rate_upper=float(self.rate_upper)

    def input_lower_rate(self):
        x,y=pag.position()
        self.y_lower_rate=y
        print("レートを入力してください")
        self.rate_lower=input()
        self.rate_lower=float(self.rate_lower)

    def convert_y2rate(self):
        dy_pix=(self.rate_upper-self.rate_lower)/(self.y_upper_rate-self.y_lower_rate)
        row=self.value_hi_trim.shape[0]

        buf=np.array([0])
        for i in range(1,row):
            buf=np.vstack((buf,np.array([self.rate_upper+dy_pix*(self.value_hi_trim[i,1]-self.y_upper_rate)])))
        self.value_hi_output=np.hstack((self.value_hi_output,buf))
        np.savetxt('test_hi.csv', self.value_hi_output,delimiter=',')

        row=self.value_low_trim.shape[0]

        buf=np.array([0])
        for i in range(1,row):
            buf=np.vstack((buf,np.array([self.rate_upper+dy_pix*(self.value_low_trim[i,1]-self.y_upper_rate)])))
        self.value_low_output=np.hstack((self.value_low_output,buf))
        np.savetxt('test_low.csv', self.value_low_output,delimiter=',')



    def on_click(self,x, y, button, pressed):

        if pressed and self.click==0:        
            (self.x1,self.y1)=pag.position()
            print('切り抜く画像の右下をクリック')
            self.click=1
        elif pressed and self.click==1:
            (self.x2,self.y2)=pag.position()
            self.click=2
        #クリックを離したら画像を保存してその数値を読みだす 
        elif pressed and self.click==2:
            #保存する画像の名前の設定 

            self.pic_name=str(self.num).zfill(3)+'.jpg'

            #まずは選択した範囲を保存 

            if self.save_pic()==-1:
                return -1#画像の保存に失敗した場合
            
            #解析 
            self.analysis()
            print('スタート時間の入力をします\nクリックしてください')

        elif pressed and self.click==3:
            self.input_start_time()
            self.click=4

            print('終了時間を入力します\nクリックしてください')
        elif pressed and self.click==4:
            self.input_end_time()
            self.convert_x2time()
            print('レートを入力します\n上をクリックしてください')
            self.click=5
        elif pressed and self.click==5:
            self.input_upper_rate()
            self.click=6
            print('下限レートを入力します')
           
        elif pressed and self.click==6:
            self.input_lower_rate()
            self.convert_y2rate()

            self.click=0
            print('処理を終了しました\n')

            #画像番号を増やす 
            #self .num+=1


    def start(self):
         with mouse.Listener(on_click=self.on_click) as self.listener:self.listener.join()



a=func()
print('切り抜く画像の左上をクリック')
a.start()

この記事が気に入ったらサポートをしてみませんか?