内容
内容
2020年12月〜2021年1月のある期間の日経平均株価チャートを自動作成
約1、2週間分程度の株価
(株に興味のない人はつまらないかも、、、)
BlenderのPythonを使うとこんなこともできるということを実践しただけの紹介動画
>>作成したローソク足の3D オブジェクトの紹介、BlenderのPython実行
ユーチューブ動画
参考サイト
https://qiita.com/kjunichi/items/a36fdc9db3876e068249
https://qiita.com/hibit/items/1ac97c71d5adc5b45f85
#日経平均株価 とは
「東京証券取引所第一部に上場する約2,000銘柄の株式のうち225銘柄を対象」原則「225銘柄の株価の合計 ÷ 225」となる。修正等ある。
https://ja.m.wikipedia.org/wiki/%E6%97%A5%E7%B5%8C%E5%B9%B3%E5%9D%87%E6%A0%AA%E4%BE%A1
「単位期間中に初めに付いた値段を始値(はじめね)、最後に付いた値段を終値(おわりね)、最も高い値段を高値(たかね)、最も安い値段を安値(やすね)」の値をもとにローソクの形状のように表現したもの
ローソク足は江戸時代に出羽国の本間宗久が発案
日経平均株価のデータを取得して、BlenderのPythonを使いローソク足の3D オブジェクトを作成した。
環境:MacOS BigSur
#Blender2.91
Blender用pythonモジュール: #pandas_datareader
準備:Pythonモジュールをインストールする必要がある。
Mac版BlenderのPythonにPip、pandas_datareaderインストールしておく。
・PythonはMacOS BigSur用とBlender用とが存在する。つまり保存場所が異なる。
よってBlenderで使いたいモジュールが必要なときは、個別でインストールが必要。
つまり、MacOSでpandas_datareaderをインストールしても、BlenderのPythonでは
使えない。
目的
:Blender2.91、Pythonスクリプトで日経平均株価データを米ヤフーファイナンスからネット上で取得して、ローソク足として3D(円柱)オブジェクトを作成する。
チャートデータは数日〜1週間程度のデータ
注意事項:
Pythonの文法の説明はほとんどしません。
BlenderのPythonの仕様について投稿者は、よく理解しておらず試行錯誤で作成し、推奨ではないコードの記述や間違いがあります。よって質問等があっても正しく答えられる知識がないため回答できない場合があります。
ご承知おきください。(他の人の回答を期待します。)
—
・Pythonスクリプトコードの概略説明
米ヤフーファイナンスから日経平均株価のデータを取得
始値、終値、高値、安値データを取得
始値より終値が大きければ陽線(赤)
小さければ陰線(青)とする
始値、終値からローソク本体の大きさ位置を決める
高値、安値からローソクのひげの大きさ位置を決める
直近の日のローソク足だけ横を向いた円錐を配置
直近のローソク足の始値終値を記載
直近の日付を記載
目盛りを配置
^読み方:キャロット
オブジェクトの原点がオブジェクトの中心に配置されていて、オブジェクトの移動やリサイズをする場合は問題は発生しないが、オブジェクトの頂点がオブジェクトの原点(中心点)より離れた位置に配置されていると、
Resizeしたときに、ポイントの縮尺が変わり、想定した位置に配置されない(バグではなく、操作や理解の問題)。
よって、リサイズして、ポイントを移動させたいときは、
オブジェクト自体を移動させてから、リサイズや個別頂点ポイントを移動させたほうが良いと思う。
・補足説明
Blenderターミナル起動(print出力を確認するためにターミナルが必要)
/Applications/Blender.app/Contents/MacOS/Blender
Blender2.91のpython場所 –MacOSpythonと異なる場所にある(バージョンが変わる毎にディレクトリが変わる。2.91の部分)
/Applications/Blender.app/Contents/Resources/2.91/python
Pythonコード
2021/01/04時点の分、Blenderで実行する。できれば、ターミナルからBlenderを起動してから、Scriptingで使用
修正履歴:2021/01/06、始値終値が重ならないように位置を調整
import os import bpy import math import copy import time import pandas_datareader.data as data from datetime import datetime,timedelta #blender 2.91 #Mac Big Sur #ターミナル起動 #/Applications/Blender.app/Contents/MacOS/Blender #GLOBAL # count # xmove # bairitsu #修正履歴 #2021/01/06:始値、終値の位置が重ならないように調整 #https://bluebirdofoz.hatenablog.com/entry/2018/04/25/231414 # オブジェクトの原点変更 # オブジェクトの原点を指定位置に移動する # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def set_origin_cursor(arg_objectname='Default',arg_location=(0,0,0)): # 他のオブジェクトの寸法を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する #ob.select=False#2.7 ob.select_set(False) #2.9 # 指定オブジェクトを取得する selectob = bpy.data.objects[arg_objectname] # 変更オブジェクトをアクティブに変更する #bpy.context.scene.objects.active = selectob#2.7 bpy.context.view_layer.objects.active=selectob#2.9 # 変更オブジェクトを選択状態にする #selectob.select=True #2.7 selectob.select_set(True) #2.9 # 3Dカーソルの元の位置を記録しておく(参照型のコピー) #cursorpos = copy.copy(bpy.context.scene.cursor_location) # 3Dカーソルの位置を指定位置に移動する #bpy.context.scene.cursor_location = arg_location#2.7 bpy.context.scene.cursor.location=arg_location#2.9 # オブジェクトの原点を3Dカーソル位置に移動する bpy.ops.object.origin_set(type='ORIGIN_CURSOR') # 3Dカーソルの位置を元に戻す #bpy.context.scene.cursor_location = cursorpos #2.7 #bpy.context.scene.cursor.location=cursorpos#2.9 not use bpy.context.scene.cursor.location=(0.0,0.0,0.0) return def fcandle(candleType,kopen,kclose,bairitsu,xmove,mat1,mat2): #count is Global global count #global xmove #global bairitsu if bairitsu=='' or bairitsu==0: print('def fcandle(),bairitsu not defined,use 0.001') bairitsu=0.001 if count=='': print('def fcandle(),count is Global, not defined, Please check, so use 1') count=1 candle=candleType #1.円柱location:図形の中心座標 radius:円の半径 depth:高さ rotation:立体の回転角(rad) bpy.ops.mesh.primitive_cylinder_add(location=(0, 0, 0), radius=0.022, depth=3, rotation=(0, 0, 0)) if candle=='yousen': bpy.context.object.data.materials.append(mat1) # 材質(赤)指定 else:#insen bpy.context.object.data.materials.append(mat2) # 材質(青)指定 bpy.ops.transform.translate(value=(xmove,0,0)) #location=bpy.context.object.location for obj in bpy.context.selected_objects: if obj.name=='Cylinder': tmpcount=count+1 obj.name = "candle"+str(tmpcount) #bpy.ops.object.shade_smooth() new_bar = bpy.context.object for vert in new_bar.data.vertices: if candle=='yousen': if vert.co[2] > 0:#Zjiku ni up vert.co[2] = kclose*bairitsu#close else: vert.co[2] = kopen*bairitsu elif candle=='insen': if vert.co[2] > 0: vert.co[2] = kopen*bairitsu else: vert.co[2] = kclose*bairitsu clocation=(xmove,0,(kopen+kclose)*bairitsu/2) set_origin_cursor(obj.name,clocation) location=bpy.context.object.location return location#candle #end def def fcandle_hige(candleType,khigh,klow,bairitsu,candleLocation,mat1,mat2): global count #global xmove #global bairitsu #count,bairitsu is Global #if bairistu=='' or bairitsu==0: # print('def fcandle_hige(),bairitsu not defined, so use 0.001') # bairitsu=0.001 if count=='': print('def fcandle_hige(),count is Global, not defined, Please check, so use 1') count=1 candle=candleType bpy.ops.mesh.primitive_cylinder_add(location=(0, 0, 0), radius=0.022/7, depth=3, rotation=(0, 0, 0)) if candle=='yousen': bpy.context.object.data.materials.append(mat1) # 材質(赤)指定 else:#insen bpy.context.object.data.materials.append(mat2) # 材質(青)指定 bpy.ops.transform.translate(value=(candleLocation[0],candleLocation[1],0))#hige location is same candle location for obj in bpy.context.selected_objects: if obj.name=='Cylinder': tmpcount=count+1 obj.name = "hige"+str(tmpcount) #xmove+=0.048 #bpy.ops.object.shade_smooth() new_bar = bpy.context.object for vert in new_bar.data.vertices: if vert.co[2] > 0:#Zjiku ni up vert.co[2] = khigh*bairitsu#close else: vert.co[2] = klow*bairitsu set_origin_cursor(obj.name,candleLocation) #end def # テキストオブジェクトの追加 def add_text(text, t_color,location): # 角度設定時に利用 ROT_QUATER = math.pi / 2 bpy.ops.object.text_add() ob = bpy.context.object ob.data.body = text ob.data.extrude = 0.05 ob.data.bevel_depth = 0.002 bpy.ops.font.open(filepath="/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc") bpy.data.fonts["HiraginoSans-W6"].name = "HiraginoSans-W6" fnt = bpy.data.fonts.load('/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc') ob.data.font = fnt ob.rotation_euler[0] = ROT_QUATER # 3Dカーソルの位置を0,0,0 bpy.context.scene.cursor.location=(0.0,0.0,0.0) #3Dカーソルを原点を変更してから、translateする必要がある。 bpy.ops.transform.translate(value=location)#26500*0.001,(1.8,0.1/3,26.5) bpy.ops.transform.resize(value=(0.2,0.1,0.2)) # 図形を変形 #ob.rotation_euler[2] = ROT_QUATER ob.data.align_x = 'CENTER' ob.data.align_y = 'CENTER' mat = bpy.data.materials.new('color_txt') mat.diffuse_color = t_color#(0.8, 0.8, 0.8, 1.0) # マテリアルスロットを追加する bpy.ops.object.material_slot_add() # 作成したマテリアルスロットに新規マテリアルを設定する bpy.context.object.active_material = mat #ob.active_material.diffuse_color = (1,0,0) #bpy.context.object.active_material.diffuse_color = (1,0,0) #mat.diffuse_color = (1.0,0.0,0.0) #mat.diffuse_color = (1,0,0)#t_color #error , I do not yet 2020/05/15 #ob.data.materials.append(mat) #ob.active_material.diffuse_color = t_color # error #convert to mesh #s = bpy.context.scene.objects.active #s.name = "text_name" bpy.ops.object.convert(target='MESH')# work for obj in bpy.context.selected_objects: if obj.name=='Text': obj.name=text return ob #end def def add_textKai(text, t_color,location,size): # 角度設定時に利用 ROT_QUATER = math.pi / 2 bpy.ops.object.text_add() ob = bpy.context.object ob.data.body = text ob.data.extrude = 0.05 ob.data.bevel_depth = 0.002 bpy.ops.font.open(filepath="/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc") bpy.data.fonts["HiraginoSans-W6"].name = "HiraginoSans-W6" fnt = bpy.data.fonts.load('/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc') ob.data.font = fnt ob.rotation_euler[0] = ROT_QUATER # 3Dカーソルの位置を0,0,0 bpy.context.scene.cursor.location=(0.0,0.0,0.0) #3Dカーソルを原点を変更してから、translateする必要がある。 bpy.ops.transform.translate(value=location)#26500*0.001,(1.8,0.1/3,26.5) bpy.ops.transform.resize(value=size) # 図形を変形(0.2,0.1,0.2) #ob.rotation_euler[2] = ROT_QUATER ob.data.align_x = 'CENTER' ob.data.align_y = 'CENTER' mat = bpy.data.materials.new('color_txt') mat.diffuse_color = t_color#(0.8, 0.8, 0.8, 1.0) # マテリアルスロットを追加する bpy.ops.object.material_slot_add() # 作成したマテリアルスロットに新規マテリアルを設定する bpy.context.object.active_material = mat #ob.active_material.diffuse_color = (1,0,0) #bpy.context.object.active_material.diffuse_color = (1,0,0) #mat.diffuse_color = (1.0,0.0,0.0) #mat.diffuse_color = (1,0,0)#t_color #error , I do not yet 2020/05/15 #ob.data.materials.append(mat) #ob.active_material.diffuse_color = t_color # error #convert to mesh #s = bpy.context.scene.objects.active #s.name = "text_name" bpy.ops.object.convert(target='MESH')# work for obj in bpy.context.selected_objects: if obj.name=='Text': obj.name=text return ob #end def #2020/12/29 #print('Max:',max(df['High']))#最大値が取れる #高値の最大値maxと安値の最小値minを取得して #500円ごとのバー基準株価線(26500,27000,27500)を自動で作成したい。 def makeBarMaxtoMin(highobj,lowobj): #print('Max:',max(highobj))#df['High'] #print('Min:',min(lowobj))#df['Low'] #bairitsu is Global global count global xmove global bairitsu #if bairistu=='' or bairitsu==0: # print('def makeBarMaxtoMin(),bairitsu not defined,use 0.001') # bairitsu=0.001 #every 500yen priceRange=500 #highPriceNumはバーの本数を取得する highPriceNum,highmod=divmod(max(highobj),priceRange)#26200/priceRange lowPriceNum,lowmod=divmod(min(lowobj),priceRange) #高値については、余りが出たら、1を追加する #print('highmod:',highmod) if highmod!=0: highPriceNum=highPriceNum+1 #Rangeで範囲を指定してしようする場合1加算では足りないので、rangeで更に1を加算する #print('after highPriceNum:',highPriceNum) for i in range(math.floor(lowPriceNum),math.floor(highPriceNum+1)): #range()が出力するのは、(highPriceNum-1)なので1を加算してhighPriceNum分までを計算させる print('barPrice:',i*priceRange) #i*priceRangeの位置でBarオブジェクトを作成する。 # 3Dカーソルの位置を0,0,0 bpy.context.scene.cursor.location=(0.0,0.0,0.0) color=(0.8,0.5,0.8,0.8) add_text(str(i*priceRange), color,(1.8,0.1/3, (i*priceRange)*bairitsu+0.045))#0.045 is ajustment bpy.context.scene.cursor.location=(0.0,0.0,0.0) # 平板作成 26500bar1 bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=2.0) bpy.ops.transform.translate(value=(0.0,0.1/2,(i*priceRange)*bairitsu))#26500*0.001 bpy.ops.transform.resize(value=(2.0,0.06/6,0.01/6)) # 図形を変形 bpy.context.object.data.materials.append(mat3) # 材質 for obj in bpy.context.selected_objects: if obj.name=='Cube': obj.name='bar_'+str(i*priceRange) #end def ###################################################################### #main ###################################################################### count=0#count is Global xmove=0.8#1# move distancs bairitsu=0.001# size Global #Stock date n=maenohi=5 end = datetime.now() #start = end - timedelta(days=maenohi)#end.day-7 changes #start = datetime(end.year, end.month, end.day-7) ##土日があると表示させるローソク足の数が少ないので、数が満たされるまでループするためSleepを入れた #大量のデータはCSVで取得してからローソク足にしたほうが良さそう x=0 while x<=(maenohi): start = end - timedelta(days=n) df=data.DataReader('^N225','yahoo',start,end)#3990.T,#^N225 date=df.index# 日付 time.sleep(3) print('len(date):',len(date)) print('3秒待ち') x=len(date) n=n+1 print(df.head(5)) if len(date)==0: print('ERROR Can not get any Price data, Check internet or yahoo finance com.') sys.exit() # 既存要素削除 for item in bpy.data.meshes: bpy.data.meshes.remove(item) for item in bpy.data.materials: bpy.data.materials.remove(item) #bpy.ops.outliner.item_rename() #2.立方体 location:図形の中心座標 size:立方体の一辺の長さ rotation:立体の回転角(rad) #bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=1.0, rotation=(0, 0, 0)) # 2.材質の定義(赤色) mat1 = bpy.data.materials.new('Red') mat1.diffuse_color = (1.0, 0.0, 0.0, 1.0) # 3.材質の定義(青色) mat2 = bpy.data.materials.new('blue') mat2.diffuse_color = (0.0, 0.0, 1.0, 1.0) mat3 = bpy.data.materials.new('gray') mat3.diffuse_color = (0.8, 0.8, 0.8, 0.5) mat5 = bpy.data.materials.new('yellow') mat5.diffuse_color = (1.0, 0.9, 0.1, 1.0) # 平板作成 27000bar #bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=2.0) #Resizeして、ポイント移動をするとポイントの縮尺が変わり、想定した位置に移動しない(推測)。 #これはオブジェクトの原点とScaleの考え方の違いかもしれない。 #よって、リサイズして、ポイントを移動させたいときは、 #リサイズをする前にオブジェクト自体を移動させてから、ポイントを移動させたほうが良いと思う。 #bpy.ops.transform.translate(value=(0.0,0.1/2,27))#27000*0.001 #bpy.ops.transform.resize(value=(2.0,0.06/6,0.01/6)) # 図形を変形 #bpy.context.object.data.materials.append(mat3) # 材質 # 平板作成 26500bar1 #bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=2.0) #bpy.ops.transform.translate(value=(0.0,0.1/2,bar1*bairitsu))#26500*0.001 #bpy.ops.transform.resize(value=(2.0,0.06/6,0.01/6)) # 図形を変形 #bpy.context.object.data.materials.append(mat3) # 材質 # 3Dカーソルの位置を0,0,0 #bpy.context.scene.cursor_location =(0.0,0.0,0.0) bpy.context.scene.cursor.location=(0.0,0.0,0.0) #26500txt #color=(0.8,0.5,0.8,0.8) #add_text('26500', color,(1.8,0.1/3,bar1*bairitsu+0.045))#0.045 is ajustment #27000txt #add_text('27000', color,(1.8,0.1/3,bar2*bairitsu+0.045)) #date=df.indexをカウントして、それぞれを取得する。ちょっとスマートではないが、、 for fdate in date: khigh=df['High'][count] klow=df['Low'][count] kopen=df['Open'][count] kclose=df['Adj Close'][count] #ここに日付ごとの処理を入れる。 # if kopen-kclose<0: candle='yousen'#color Red #print('陽線') else: candle='insen'#color Blue #print('陰線') #objを生成する。 #始値、終値でZ軸の高さを設定する。 print( fdate.strftime("%Y/%m/%d") )#2020-12-22 00:00:00となるので、時刻を除く2020-12-22 #Candle_Main#################################################### candleLocation=fcandle(candle,kopen,kclose,bairitsu,xmove,mat1,mat2) xmove+=0.048 #Candle_hige#################################################### fcandle_hige(candle,khigh,klow,bairitsu,candleLocation,mat1,mat2) #count is Global count+=1 #最後のローソクのobjの横に矢印を与える #この場合、ローソクの原点を移動させて、ローソクの中心に移動させる必要がある candleCount=0 for obj in bpy.data.objects: if 'candle' in obj.name: #print(obj.name) candleCount+=1 candleName='candle'+str(candleCount) #Arrow bpy.ops.mesh.primitive_cone_add(radius1=1, radius2=0, depth=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(0.05, 0.05, 0.05)) #図形を回転(Y軸周りに90°) bpy.ops.transform.rotate(value=-3.1415/2 ,orient_axis='Y') bpy.context.object.data.materials.append(mat5) bpy.ops.transform.translate(value=(bpy.data.objects[candleName].location[0]+0.08,bpy.data.objects[candleName].location[1],bpy.data.objects[candleName].location[2]))# bpy.context.scene.cursor.location=(0.0,0.0,0.0) #open,close color=(1.0,1.0,1.0,1.0) #str,color,location,size lastOpen=df['Open'][len(date)-1]#始値 lastClose=df['Close'][len(date)-1]#終値 roundOpen='O:'+str(round(lastOpen,2))#画面に表示させる直近の始値文字 roundClose='C:'+str(round(lastClose,2))#画面に表示させる直近の終値文字 add_textKai(roundOpen, color,(xmove+0.1,0,lastOpen*bairitsu),(0.06,0.05,0.06)) add_textKai(roundClose, color,(xmove+0.1,0,lastClose*bairitsu),(0.06,0.05,0.06)) #end.strftime("%Y/%m/%d") or date[len(date)-1].strftime("%Y/%m/%d")today color=(0.0,1.0,0.0,1.0) coneLocation=bpy.data.objects['Cone'].location tyokkinDay=date[len(date)-1].strftime("%Y/%m/%d") if len(date)>0: add_textKai(tyokkinDay,color, (coneLocation[0]+0.16,coneLocation[1],coneLocation[2]),(0.06,0.05,0.06)) #print('last day:',date[len(date)-1]) #始値、終値、直近の日付の調整。オブジェクトが重なってしまう金額が近いときに離す処理 if candle=='yousen':#Openが下側でCloseが上側にあるはず #bpy.data.objects[roundOpen].dimensions[1]#Z方向の幅サイズではなくてY軸、理由はローカルで回転させたから #bpy.data.objects[roundClose].dimensions[1] #bpy.data.objects[tyokkinDay].dimensions[1] #始値、終値、直近の日付のlocationを取得する。Z方向ではなくてY軸の幅の半分を上下に加減算して重なりを調べる #bpy.data.objects[roundOpen].location[2] #bpy.data.objects[roundClose].location[2] #bpy.data.objects[tyokkinDay].location[2] #roundCloseを調整する.roundCloseの下側位置が、直近日付の上側位置以下になっているときに、直近日付の上の方にroundCloseを上へ移動させる #location is [2]:Z, dimensions is [1]:Y if bpy.data.objects[roundClose].location[2]-bpy.data.objects[roundClose].dimensions[1]/2 <= bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]/2: #直近日付の位置に、プラスへ移動 bpy.data.objects[roundClose].location[2]=bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]*1.5 #roundOpenを調整する。roundOpenの「上側」位置が、直近日付の「下側」以上になっているときに、直近日付の「下の方」にroundOpenを下へ移動させる if bpy.data.objects[roundOpen].location[2]+bpy.data.objects[roundOpen].dimensions[1]/2 >=bpy.data.objects[tyokkinDay].location[2]-bpy.data.objects[tyokkinDay].dimensions[1]/2: #「マイナス」へ移動 bpy.data.objects[roundOpen].location[2]=bpy.data.objects[tyokkinDay].location[1]-bpy.data.objects[tyokkinDay].dimensions[1]*1.5 else:#insen Openが上側でCloseが下側にあるはず.Yousenとは逆 #roundOpen print('insen') if bpy.data.objects[roundOpen].location[2]-bpy.data.objects[roundOpen].dimensions[1]/2<=bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]/2: bpy.data.objects[roundOpen].location[2]=bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]*1.5 print('Open') #roundClose if bpy.data.objects[roundClose].location[2]+bpy.data.objects[roundClose].dimensions[1]/2>=bpy.data.objects[tyokkinDay].location[2]-bpy.data.objects[tyokkinDay].dimensions[1]/2: bpy.data.objects[roundClose].location[2]=bpy.data.objects[tyokkinDay].location[2]-bpy.data.objects[tyokkinDay].dimensions[1]*1.5 print('Close') #2020/12/29 #print('Max:',max(df['High']))#最大値が取れる #高値の最大値maxと安値の最小値minを取得して #500円ごとのバー基準株価線(26500,27000,27500)を自動で作成したい。 makeBarMaxtoMin(df['High'],df['Low']) #camera move, near Cone obj camera_obj = bpy.data.objects['Camera'] print(camera_obj.location) camera_obj.location=(coneLocation[0]+0.16,coneLocation[1]-3.0,coneLocation[2]) print('done.') ''' Result '''
別コード、株価を記載したCSVファイルを読み込んで立体ローソク足にした
米ヤフーファイナンスに接続して当日にデータを取得すると、前日データが欠落するし、データが公開されるまで時間がかかる(20分)ので
CSVで当日データを記載したファイルを読み込んだほうが早いと思ってCSV読み取りでローソク足を立体化するPythonコードを書いた。日経平均株価データのクラスオブジェクト、カプセルデータとして扱う。CSVの場合は、数字が文字列扱いになるので、floatにするなどの型変換が必要だった。
なお、このコードには、別ファイルのBlendファイルからオブジェクト(猫)を読み込むコードを記載してある。
ファイルパスはフルパス。
import os import bpy import math import copy import time import pandas_datareader.data as data from datetime import datetime,timedelta #blender 2.91 #Mac Big Sur #ターミナル起動 #/Applications/Blender.app/Contents/MacOS/Blender #GLOBAL # count # xmove # bairitsu #日経平均株価データのクラスオブジェクト、カプセルデータ class nikkeiClass: def __init__(self, cindex, chigh,clow,copen,cclose,cadjclose): self.index = cindex self.high = chigh self.low = clow self.open = copen self.close = cclose self.adjclose = cadjclose #https://bluebirdofoz.hatenablog.com/entry/2018/04/25/231414 # オブジェクトの原点変更 # オブジェクトの原点を指定位置に移動する # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def set_origin_cursor(arg_objectname='Default',arg_location=(0,0,0)): # 他のオブジェクトの寸法を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する #ob.select=False#2.7 ob.select_set(False) #2.9 # 指定オブジェクトを取得する selectob = bpy.data.objects[arg_objectname] # 変更オブジェクトをアクティブに変更する #bpy.context.scene.objects.active = selectob#2.7 bpy.context.view_layer.objects.active=selectob#2.9 # 変更オブジェクトを選択状態にする #selectob.select=True #2.7 selectob.select_set(True) #2.9 # 3Dカーソルの元の位置を記録しておく(参照型のコピー) #cursorpos = copy.copy(bpy.context.scene.cursor_location) # 3Dカーソルの位置を指定位置に移動する #bpy.context.scene.cursor_location = arg_location#2.7 bpy.context.scene.cursor.location=arg_location#2.9 # オブジェクトの原点を3Dカーソル位置に移動する bpy.ops.object.origin_set(type='ORIGIN_CURSOR') # 3Dカーソルの位置を元に戻す #bpy.context.scene.cursor_location = cursorpos #2.7 #bpy.context.scene.cursor.location=cursorpos#2.9 not use bpy.context.scene.cursor.location=(0.0,0.0,0.0) return def fcandle(candleType,kopen,kclose,bairitsu,xmove,mat1,mat2): #count is Global global count #global xmove #global bairitsu if bairitsu=='' or bairitsu==0: print('def fcandle(),bairitsu not defined,use 0.001') bairitsu=0.001 if count=='': print('def fcandle(),count is Global, not defined, Please check, so use 1') count=1 candle=candleType #1.円柱location:図形の中心座標 radius:円の半径 depth:高さ rotation:立体の回転角(rad) bpy.ops.mesh.primitive_cylinder_add(location=(0, 0, 0), radius=0.022, depth=3, rotation=(0, 0, 0)) if candle=='yousen': bpy.context.object.data.materials.append(mat1) # 材質(赤)指定 else:#insen bpy.context.object.data.materials.append(mat2) # 材質(青)指定 bpy.ops.transform.translate(value=(xmove,0,0)) #location=bpy.context.object.location for obj in bpy.context.selected_objects: if obj.name=='Cylinder': tmpcount=count+1 obj.name = "candle"+str(tmpcount) #bpy.ops.object.shade_smooth() new_bar = bpy.context.object for vert in new_bar.data.vertices: if candle=='yousen': if vert.co[2] > 0:#Zjiku ni up vert.co[2] = kclose*bairitsu#close else: vert.co[2] = kopen*bairitsu elif candle=='insen': if vert.co[2] > 0: vert.co[2] = kopen*bairitsu else: vert.co[2] = kclose*bairitsu clocation=(xmove,0,(kopen+kclose)*bairitsu/2) set_origin_cursor(obj.name,clocation) location=bpy.context.object.location return location#candle #end def def fcandle_hige(candleType,khigh,klow,bairitsu,candleLocation,mat1,mat2): global count #global xmove #global bairitsu #count,bairitsu is Global #if bairistu=='' or bairitsu==0: # print('def fcandle_hige(),bairitsu not defined, so use 0.001') # bairitsu=0.001 if count=='': print('def fcandle_hige(),count is Global, not defined, Please check, so use 1') count=1 candle=candleType bpy.ops.mesh.primitive_cylinder_add(location=(0, 0, 0), radius=0.022/7, depth=3, rotation=(0, 0, 0)) if candle=='yousen': bpy.context.object.data.materials.append(mat1) # 材質(赤)指定 else:#insen bpy.context.object.data.materials.append(mat2) # 材質(青)指定 bpy.ops.transform.translate(value=(candleLocation[0],candleLocation[1],0))#hige location is same candle location for obj in bpy.context.selected_objects: if obj.name=='Cylinder': tmpcount=count+1 obj.name = "hige"+str(tmpcount) #xmove+=0.048 #bpy.ops.object.shade_smooth() new_bar = bpy.context.object for vert in new_bar.data.vertices: if vert.co[2] > 0:#Zjiku ni up vert.co[2] = khigh*bairitsu#close else: vert.co[2] = klow*bairitsu set_origin_cursor(obj.name,candleLocation) #end def # テキストオブジェクトの追加 def add_text(text, t_color,location): # 角度設定時に利用 ROT_QUATER = math.pi / 2 bpy.ops.object.text_add() ob = bpy.context.object ob.data.body = text ob.data.extrude = 0.05 ob.data.bevel_depth = 0.002 bpy.ops.font.open(filepath="/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc") bpy.data.fonts["HiraginoSans-W6"].name = "HiraginoSans-W6" fnt = bpy.data.fonts.load('/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc') ob.data.font = fnt ob.rotation_euler[0] = ROT_QUATER # 3Dカーソルの位置を0,0,0 bpy.context.scene.cursor.location=(0.0,0.0,0.0) #3Dカーソルを原点を変更してから、translateする必要がある。 bpy.ops.transform.translate(value=location)#26500*0.001,(1.8,0.1/3,26.5) bpy.ops.transform.resize(value=(0.2,0.1,0.2)) # 図形を変形 #ob.rotation_euler[2] = ROT_QUATER ob.data.align_x = 'CENTER' ob.data.align_y = 'CENTER' mat = bpy.data.materials.new('color_txt') mat.diffuse_color = t_color#(0.8, 0.8, 0.8, 1.0) # マテリアルスロットを追加する bpy.ops.object.material_slot_add() # 作成したマテリアルスロットに新規マテリアルを設定する bpy.context.object.active_material = mat #ob.active_material.diffuse_color = (1,0,0) #bpy.context.object.active_material.diffuse_color = (1,0,0) #mat.diffuse_color = (1.0,0.0,0.0) #mat.diffuse_color = (1,0,0)#t_color #error , I do not yet 2020/05/15 #ob.data.materials.append(mat) #ob.active_material.diffuse_color = t_color # error #convert to mesh #s = bpy.context.scene.objects.active #s.name = "text_name" bpy.ops.object.convert(target='MESH')# work for obj in bpy.context.selected_objects: if obj.name=='Text': obj.name=text return ob #end def def add_textKai(text, t_color,location,size): # 角度設定時に利用 ROT_QUATER = math.pi / 2 bpy.ops.object.text_add() ob = bpy.context.object ob.data.body = text ob.data.extrude = 0.05 ob.data.bevel_depth = 0.002 bpy.ops.font.open(filepath="/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc") bpy.data.fonts["HiraginoSans-W6"].name = "HiraginoSans-W6" fnt = bpy.data.fonts.load('/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc') ob.data.font = fnt ob.rotation_euler[0] = ROT_QUATER # 3Dカーソルの位置を0,0,0 bpy.context.scene.cursor.location=(0.0,0.0,0.0) #3Dカーソルを原点を変更してから、translateする必要がある。 bpy.ops.transform.translate(value=location)#26500*0.001,(1.8,0.1/3,26.5) bpy.ops.transform.resize(value=size) # 図形を変形(0.2,0.1,0.2) #ob.rotation_euler[2] = ROT_QUATER ob.data.align_x = 'CENTER' ob.data.align_y = 'CENTER' mat = bpy.data.materials.new('color_txt') mat.diffuse_color = t_color#(0.8, 0.8, 0.8, 1.0) # マテリアルスロットを追加する bpy.ops.object.material_slot_add() # 作成したマテリアルスロットに新規マテリアルを設定する bpy.context.object.active_material = mat #ob.active_material.diffuse_color = (1,0,0) #bpy.context.object.active_material.diffuse_color = (1,0,0) #mat.diffuse_color = (1.0,0.0,0.0) #mat.diffuse_color = (1,0,0)#t_color #error , I do not yet 2020/05/15 #ob.data.materials.append(mat) #ob.active_material.diffuse_color = t_color # error #convert to mesh #s = bpy.context.scene.objects.active #s.name = "text_name" bpy.ops.object.convert(target='MESH')# work for obj in bpy.context.selected_objects: if obj.name=='Text': obj.name=text return ob #end def #2020/12/29 #print('Max:',max(df['High']))#最大値が取れる #高値の最大値maxと安値の最小値minを取得して #500円ごとのバー基準株価線(26500,27000,27500)を自動で作成したい。 def makeBarMaxtoMin(highobj,lowobj): #print('Max:',max(highobj))#df['High'] #print('Min:',min(lowobj))#df['Low'] #bairitsu is Global global count global xmove global bairitsu #if bairistu=='' or bairitsu==0: # print('def makeBarMaxtoMin(),bairitsu not defined,use 0.001') # bairitsu=0.001 #every 500yen priceRange=500 #highPriceNumはバーの本数を取得する highPriceNum,highmod=divmod(max(highobj),priceRange)#26200/priceRange lowPriceNum,lowmod=divmod(min(lowobj),priceRange) #高値については、余りが出たら、1を追加する #print('highmod:',highmod) if highmod!=0: highPriceNum=highPriceNum+1 #Rangeで範囲を指定してしようする場合1加算では足りないので、rangeで更に1を加算する #print('after highPriceNum:',highPriceNum) for i in range(math.floor(lowPriceNum),math.floor(highPriceNum+1)): #range()が出力するのは、(highPriceNum-1)なので1を加算してhighPriceNum分までを計算させる print('barPrice:',i*priceRange) #i*priceRangeの位置でBarオブジェクトを作成する。 # 3Dカーソルの位置を0,0,0 bpy.context.scene.cursor.location=(0.0,0.0,0.0) color=(0.8,0.5,0.8,0.8) add_text(str(i*priceRange), color,(1.8,0.1/3, (i*priceRange)*bairitsu+0.045))#0.045 is ajustment bpy.context.scene.cursor.location=(0.0,0.0,0.0) # 平板作成 26500bar1 bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=2.0) bpy.ops.transform.translate(value=(0.0,0.1/2,(i*priceRange)*bairitsu))#26500*0.001 bpy.ops.transform.resize(value=(2.0,0.06/6,0.01/6)) # 図形を変形 bpy.context.object.data.materials.append(mat3) # 材質 for obj in bpy.context.selected_objects: if obj.name=='Cube': obj.name='bar_'+str(i*priceRange) #end def ###################################################################### #main ###################################################################### count=0#count is Global xmove=0.8#1# move distancs bairitsu=0.001# size Global print('this Code for reading CSV.') print('>>>>>CSV出力>>>>>') #ここからCSV出力用 checkLineCount=0 df=[] #CSVを読み込む ifile='/Users/toshiromaseda/Documents/blender/script/2021年1月14日日経.csv' try:#ファイルが存在しないときのエラー処理try with open(ifile,'tr') as fin: for iline in fin: try: tmp=iline.strip().split(",")#stripしてからsplit()だと理解している。 if 'High' in tmp[0]: continue if len(tmp) < 6:#全部で6列存在する。 print('ERROR 列の数があっていません。少ないようです。行数:',checkLineCount) break print('iline[]:',tmp[0]) #ここから処理を入れていけばよい df.append(nikkeiClass(tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5])) checkLineCount+=1 except: print ('error') except FileNotFoundError as e: # FileNotFoundErrorは例外クラス名 print("ファイルが見つかりません。パス、ファイル名を確認してください", e) ifile.close() sys._exit()#ファイルがなければ終了 #Stock date n=maenohi=5 end = datetime.now() start = end - timedelta(days=n) #start = end - timedelta(days=maenohi)#end.day-7 changes #start = datetime(end.year, end.month, end.day-7) ##土日があると表示させるローソク足の数が少ないので、数が満たされるまでループするためSleepを入れた #大量のデータはCSVで取得してからローソク足にしたほうが良さそう ''' x=0 while x<=(maenohi): start = end - timedelta(days=n) df=data.DataReader('^N225','yahoo',start,end)#3990.T,#^N225 date=df.index# 日付 time.sleep(3) print('len(date):',len(date)) print('3秒待ち') x=len(date) n=n+1 print(df.head(8)) if len(date)==0: print('ERROR Can not get any Price data, Check internet or yahoo finance com.') sys.exit() ''' # 既存要素削除 for item in bpy.data.meshes: bpy.data.meshes.remove(item) for item in bpy.data.materials: bpy.data.materials.remove(item) #bpy.ops.outliner.item_rename() #2.立方体 location:図形の中心座標 size:立方体の一辺の長さ rotation:立体の回転角(rad) #bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=1.0, rotation=(0, 0, 0)) # 2.材質の定義(赤色) mat1 = bpy.data.materials.new('Red') mat1.diffuse_color = (1.0, 0.0, 0.0, 1.0) # 3.材質の定義(青色) mat2 = bpy.data.materials.new('blue') mat2.diffuse_color = (0.0, 0.0, 1.0, 1.0) mat3 = bpy.data.materials.new('gray') mat3.diffuse_color = (0.8, 0.8, 0.8, 0.5) mat5 = bpy.data.materials.new('yellow') mat5.diffuse_color = (1.0, 0.9, 0.1, 1.0) # 3Dカーソルの位置を0,0,0 #bpy.context.scene.cursor_location =(0.0,0.0,0.0) bpy.context.scene.cursor.location=(0.0,0.0,0.0) #date=df.indexをカウントして、それぞれを取得する。ちょっとスマートではないが、、 for fdate in df: khigh=float(fdate.high) klow=float(fdate.low) kopen=float(fdate.open) kclose=float(fdate.adjclose) #ここに日付ごとの処理を入れる。 # if kopen-kclose<0: candle='yousen'#color Red #print('陽線') else: candle='insen'#color Blue #print('陰線') #objを生成する。 #始値、終値でZ軸の高さを設定する。 #print( fdate.strftime("%Y/%m/%d") )#2020-12-22 00:00:00となるので、時刻を除く2020-12-22 #Candle_Main#################################################### candleLocation=fcandle(candle,kopen,kclose,bairitsu,xmove,mat1,mat2) xmove+=0.048 #Candle_hige#################################################### fcandle_hige(candle,khigh,klow,bairitsu,candleLocation,mat1,mat2) #count is Global count+=1 #最後のローソクのobjの横に矢印を与える #この場合、ローソクの原点を移動させて、ローソクの中心に移動させる必要がある candleCount=0 for obj in bpy.data.objects: if 'candle' in obj.name: #print(obj.name) candleCount+=1 candleName='candle'+str(candleCount) #Arrow bpy.ops.mesh.primitive_cone_add(radius1=1, radius2=0, depth=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(0.05, 0.05, 0.05)) #図形を回転(Y軸周りに90°) bpy.ops.transform.rotate(value=-3.1415/2 ,orient_axis='Y') bpy.context.object.data.materials.append(mat5) bpy.ops.transform.translate(value=(bpy.data.objects[candleName].location[0]+0.08,bpy.data.objects[candleName].location[1],bpy.data.objects[candleName].location[2]))# bpy.context.scene.cursor.location=(0.0,0.0,0.0) #open,close color=(1.0,1.0,1.0,1.0) #str,color,location,size lastOpen=float(df[len(df)-1].open)#始値 lastClose=float(df[len(df)-1].close)#終値 roundOpen='O:'+str(round(lastOpen,2))#画面に表示させる直近の始値文字 roundClose='C:'+str(round(lastClose,2))#画面に表示させる直近の終値文字 add_textKai(roundOpen, color,(xmove+0.1,0,lastOpen*bairitsu),(0.06,0.05,0.06)) add_textKai(roundClose, color,(xmove+0.1,0,lastClose*bairitsu),(0.06,0.05,0.06)) #end.strftime("%Y/%m/%d") or date[len(date)-1].strftime("%Y/%m/%d")today color=(0.0,1.0,0.0,1.0) coneLocation=bpy.data.objects['Cone'].location #tyokkinDay=date[len(date)-1].strftime("%Y/%m/%d") tyokkinDay=df[len(df)-1].index if len(df)>0: add_textKai(tyokkinDay,color, (coneLocation[0]+0.16,coneLocation[1],coneLocation[2]),(0.06,0.05,0.06)) #print('last day:',date[len(date)-1]) #始値、終値、直近の日付の調整。オブジェクトが重なってしまう金額が近いときに離す処理 if candle=='yousen':#Openが下側でCloseが上側にあるはず #bpy.data.objects[roundOpen].dimensions[1]#Z方向の幅サイズではなくてY軸、理由はローカルで回転させたから #bpy.data.objects[roundClose].dimensions[1] #bpy.data.objects[tyokkinDay].dimensions[1] #始値、終値、直近の日付のlocationを取得する。Z方向ではなくてY軸の幅の半分を上下に加減算して重なりを調べる #bpy.data.objects[roundOpen].location[2] #bpy.data.objects[roundClose].location[2] #bpy.data.objects[tyokkinDay].location[2] #roundCloseを調整する.roundCloseの下側位置が、直近日付の上側位置以下になっているときに、直近日付の上の方にroundCloseを上へ移動させる #location is [2]:Z, dimensions is [1]:Y if bpy.data.objects[roundClose].location[2]-bpy.data.objects[roundClose].dimensions[1]/2 <= bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]/2: #直近日付の位置に、プラスへ移動 bpy.data.objects[roundClose].location[2]=bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]*1.5 #roundOpenを調整する。roundOpenの「上側」位置が、直近日付の「下側」以上になっているときに、直近日付の「下の方」にroundOpenを下へ移動させる if bpy.data.objects[roundOpen].location[2]+bpy.data.objects[roundOpen].dimensions[1]/2 >=bpy.data.objects[tyokkinDay].location[2]-bpy.data.objects[tyokkinDay].dimensions[1]/2: #「マイナス」へ移動 bpy.data.objects[roundOpen].location[2]=bpy.data.objects[tyokkinDay].location[1]-bpy.data.objects[tyokkinDay].dimensions[1]*1.5 else:#insen Openが上側でCloseが下側にあるはず.Yousenとは逆 #roundOpen print('insen') if bpy.data.objects[roundOpen].location[2]-bpy.data.objects[roundOpen].dimensions[1]/2<=bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]/2: bpy.data.objects[roundOpen].location[2]=bpy.data.objects[tyokkinDay].location[2]+bpy.data.objects[tyokkinDay].dimensions[1]*1.5 print('Open') #roundClose if bpy.data.objects[roundClose].location[2]+bpy.data.objects[roundClose].dimensions[1]/2>=bpy.data.objects[tyokkinDay].location[2]-bpy.data.objects[tyokkinDay].dimensions[1]/2: bpy.data.objects[roundClose].location[2]=bpy.data.objects[tyokkinDay].location[2]-bpy.data.objects[tyokkinDay].dimensions[1]*1.5 print('Close') #2020/12/29 #print('Max:',max(df['High']))#最大値が取れる #高値の最大値maxと安値の最小値minを取得して #500円ごとのバー基準株価線(26500,27000,27500)を自動で作成したい。 #面倒だけど、別途高値、安値の配列オブジェクトを作って、メソッドに渡す dfHigh=[] dfLow=[] for m in df: dfHigh.append(float(m.high)) dfLow.append(float(m.low)) makeBarMaxtoMin(dfHigh,dfLow) #camera move, near Cone obj camera_obj = bpy.data.objects['Camera'] print(camera_obj.location) camera_obj.location=(coneLocation[0]+0.16,coneLocation[1]-3.0,coneLocation[2]) #Neko呼び出し file_path = '/Users/toshiromaseda/Documents/blender/model/テスト、研究モデル/Neko/Neko_simple_001.blend' inner_path = 'Object' object_name = 'Neko' bpy.ops.wm.append( filepath=os.path.join(file_path, inner_path, object_name), directory=os.path.join(file_path, inner_path), filename=object_name ) bpy.context.scene.cursor.location=(0.0,0.0,0.0) # 指定オブジェクトを取得する #arg_objectname='Neko' selectob = bpy.data.objects[object_name] selectob.select_set(True) #2.9 selectob.location=(coneLocation[0],coneLocation[1]-0.1,coneLocation[2]) bpy.ops.transform.resize(value=(0.07,0.07,0.07)) # 図形を変形 print('done.') ''' Result '''