※ Elliott Pattern Helper Add In
지난 글('Excel에서 HTS DDE 활용하기 4 - 데이터 축적 I)에서는 선택된 종목 하나에 대해서만 캔들정보를 축적하도록 했었습니다. 여러 종목에 대해 동일한 작업을 하도록 하는 것도 당연히 가능하겠지요?
이번 글의 목표는 현재까지 완성된 파일을 수정하여 다수 종목의 데이터를 축적하게끔 하는 것입니다. 즉, 시나리오는 다음과 같습니다.
- 사용자가 UserForm('사용자 정의 폼')을 통해 다수의 종목을 선택하고, 스케줄 시각을 지정하면, 지정된 시간 동안 선택된 종목들의 캔들정보가 축적된다.
한 개의 종목에 한해서만 수행되던 폼과 Sub모듈 및 함수들을 어떻게 수정하면 될까요? 우선, 폼이 여러 개의 종목을 선택할 수 있게끔 변경되어야 겠군요. 지금까지 우리가 작업해 왔던 폼을 보겠습니다.
그림 1. 'DDE Schedule' 폼 |
스케줄 시작 및 종료 시각을 지정하는 콤보 상자 두 개와 캔들의 시간단위를 선택하는 콤보 상자는 수정하지 않고 그대로 사용해도 될 듯합니다.
종목 선택을 위한 콤보 상자는, 단일 종목만을 선택할 수 있으므로, 다중선택(multiselection)이 가능한 목록 상자(ListBox)로 수정해야 하겠습니다.
'개발 도구' → 'Visual Basic'을 선택하거나 'Alt + F11'을 눌러 VBA 편집창을 활성화시킨 후 폼의 '개체 보기' 모드로 들어갑니다. 다음 그림과 같이 다중 페이지의 Context 메뉴 → '새 페이지'를 선택하여 페이지를 추가합니다.
그림 2. 'DDE Schedule' 폼의 페이지 추가 |
다음 그림 3과 같이 새 페이지에 목록 상자(ListBox)를 추가하고 이름을 'ListBoxSN'으로 합니다. 목록 상자 앞의 레이블은 적당히 입력하십시요.
그림 3. 목록 상자 'ListBoxSN' 추가 |
다수의 항목을 선택할 수 있어야 하므로, 새로 추가한 목록 상자 'ListBoxSN'의 속성 중 MultiSelect 항목을 다음 그림과 같이 '1'로 설정하십시요.
그림 4. 'ListBoxSN'의 MultiSelect 속성 선택 |
VBAProject 트리 목록에서 'FSchedule' 폼의 Context 메뉴 → '코드 보기'를 선택하여 소스코드 편집 모드로 들어갑니다. 기존에 있던 종목 선택 콤보 상자의 초기화 로직을 새로 추가한 목록 상자를 초기화하기 위한 것으로 수정합니다. 그림 5에서 붉은 색 선으로 표시된 부분을 하단의 소스 중 '(변경 후 - ListBoxSN 초기화)' 소스로 대체합니다.
그림 5. 폼의 초기화('UserForm_Initialize') Sub모듈 수정 |
※ (변경 전 - ComboBoxSN 초기화)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
s_list = getStockList()
For i = LBound(s_list, 1) To UBound(s_list, 1)
If s_list(i, 1) <> "" Then _
ComboBoxSN.AddItem s_list(i, 2) & "(" & s_list(i, 1) & ")", i - 1
Next i
ComboBoxSN.ListIndex = i - 2
For i = LBound(s_list, 1) To UBound(s_list, 1)
If s_list(i, 1) <> "" Then _
ComboBoxSN.AddItem s_list(i, 2) & "(" & s_list(i, 1) & ")", i - 1
Next i
ComboBoxSN.ListIndex = i - 2
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
※ (변경 후 - ListBoxSN 초기화)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
s_list = getStockList()For i = LBound(s_list, 1) To UBound(s_list, 1)
If s_list(i, 1) <> "" Then _
ListBoxSN.AddItem s_list(i, 2) & "(" & s_list(i, 1) & ")" ', i - 1
Next i
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
위 그림 5의 상단에 'ButtonOK_Click' Sub모듈이 보입니다. 폼에서 'OK' 버튼을 클릭했을 때 수행되는데, 기존에 있던 콤보 상자 'ComboBoxSN'에서 선택된 종목을 인자로 넘겨주면서 'onSchedule' 모듈을 호출합니다. 이 부분을 수정해야겠지요? 하단의 소스를 복사하여 다음 그림과 같이 대체하십시요.
그림 6. 'ButtonOK_Click' 이벤트 처리 모듈 수정 |
※ (변경 후 'ButtonOK_Click' 소스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub ButtonOK_Click()Dim s_list As String 'comma separated selected items list
For i = 0 To ListBoxSN.ListCount - 1
If ListBoxSN.Selected(i) = True Then
s_list = s_list & ListBoxSN.List(i) & ","
End If
Next i
If Len(s_list) > 0 Then
s_list = Left(s_list, Len(s_list) - 1)
Call onSchedule(ComboBoxTS.Text, ComboBoxTE.Text, _
s_list, ComboBoxCT.Text)
End If
Me.Hide
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
☞ 'onSchedule'로 넘겨주는 인자가 단일 종목을 넘겨주던 것에서 (comma-separated) 복수 종목으로 변경됩니다.
폼의 다중 페이지 중 새로 생성한 'Page3'의 Caption을 적절히 수정하고, 기존 콤보 상자 'ComboBoxSN'이 있는 페이지로 이동합니다.
캔들의 시간단위를 선택하기 위한 콤보 상자 'ComboBoxCT'는 그대로 사용할 것이므로 다중 페이지의 첫 번째 페이지('Time')로 이동시키고, 나머지는 페이지와 함께 삭제하도록 하겠습니다. 완성된 폼은 다음 그림과 같습니다.
그림 7. 완성된 폼 |
폼의 'ButtonOK_Click'이 호출하는 'onSchedule' Sub모듈의 소스를 보겠습니다.
그림 8. 'onSchedule' 소스 |
'prepareSheet'와 'Application.OnTime'으로 인자(parameter)들을 중계하는 역할만 수행하므로 'onSchedule' 소스를 수정할 필요는 없겠군요. 그림 8의 하단에서 찾을 수 있는 'Stop_Click'은 지난 세 편의 글에서도 그랬듯이 그대로 사용하면 될 것입니다.
따라서, 남은 작업은 'prepareSheet'와 'Start_Click'에서 처리되는 로직을 수정하는 것입니다.
먼저 'prepareSheet' 함수부터 수정하겠습니다. 기존의 'prepareSheet'는, 선택된 한 개의 종목에 대해, 종목코드와 종목명을 전역변수에 저장한 후 캔들정보 축적 시트를 생성합니다. 이것을 다수의 종목에 대해 동일한 작업을 하도록 수정하기만 하면 됩니다. 기존 함수를 아래 소스로 대체하십시요.
※ (변경 후 'prepareSheet' 소스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function prepareSheet(slist, cu)
Dim xlSheet As Worksheet
Dim s_arr As Variant
On Error GoTo EH_prepareSheet:
dde_sheet = ActiveSheet.Name
s_arr = Split(slist, ",")
s_cnt = UBound(s_arr) - LBound(s_arr) + 1
ReDim ss_list(1 To s_cnt, 1 To 2)
For i = 1 To s_cnt
sn = s_arr(LBound(s_arr) + i - 1)
s_code = Right(sn, Len(sn) - InStr(sn, "("))
s_code = Replace(s_code, ")", "")
If s_code = Empty Or s_code = "" Then GoTo NextLoop:
sh_name = Left(sn, InStr(sn, "(") - 1)
If sh_name = Empty Or sh_name = "" Then sh_name = s_code
sh_name = sh_name & "-" & cu
ss_list(i, 1) = s_code
ss_list(i, 2) = sh_name
If Not sheetExist(sh_name) Then
Set xlSheet = Worksheets.Add(After:=Worksheets(Worksheets.Count))
xlSheet.Name = sh_name
With xlSheet
.Cells(1, 1).Value = "일자 / 시간"
.Cells(1, 2).Value = "시가"
.Cells(1, 3).Value = "고가"
.Cells(1, 4).Value = "저가"
.Cells(1, 5).Value = "종가"
.Cells(1, 6).Value = "거래량"
End With
End If
NextLoop:
Next i
Set xlSheet = Nothing
Exit Function
EH_prepareSheet:
MsgBox "Error(" & Err.Number & ") " & Err.Description & " [prepareSheet]"
Err.Raise Err.Number, "prepareSheet", Err.Description
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function prepareSheet(slist, cu)
Dim xlSheet As Worksheet
Dim s_arr As Variant
On Error GoTo EH_prepareSheet:
dde_sheet = ActiveSheet.Name
s_arr = Split(slist, ",")
s_cnt = UBound(s_arr) - LBound(s_arr) + 1
ReDim ss_list(1 To s_cnt, 1 To 2)
For i = 1 To s_cnt
sn = s_arr(LBound(s_arr) + i - 1)
s_code = Right(sn, Len(sn) - InStr(sn, "("))
s_code = Replace(s_code, ")", "")
If s_code = Empty Or s_code = "" Then GoTo NextLoop:
sh_name = Left(sn, InStr(sn, "(") - 1)
If sh_name = Empty Or sh_name = "" Then sh_name = s_code
sh_name = sh_name & "-" & cu
ss_list(i, 1) = s_code
ss_list(i, 2) = sh_name
If Not sheetExist(sh_name) Then
Set xlSheet = Worksheets.Add(After:=Worksheets(Worksheets.Count))
xlSheet.Name = sh_name
With xlSheet
.Cells(1, 1).Value = "일자 / 시간"
.Cells(1, 2).Value = "시가"
.Cells(1, 3).Value = "고가"
.Cells(1, 4).Value = "저가"
.Cells(1, 5).Value = "종가"
.Cells(1, 6).Value = "거래량"
End With
End If
NextLoop:
Next i
Set xlSheet = Nothing
Exit Function
EH_prepareSheet:
MsgBox "Error(" & Err.Number & ") " & Err.Description & " [prepareSheet]"
Err.Raise Err.Number, "prepareSheet", Err.Description
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
☞ 'slist' 인자가 comma로 분리된 '종목명(종목코드)' 리스트입니다.
다음은 'Start_Click'을 통해 수행되는 로직을 수정할 차례입니다. 다음 그림 9에서 알 수 있듯이 'storeCandleInfo' 모듈만 수행하면 되겠군요.
그림 9. 'Start_Click' 소스 |
'storeCandleInfo' Sub모듈은 처리속도 향상을 위해 마지막으로 저장한 캔들정보의 상태를 유지하면서 새로 수신한 체결정보에 대해 기존 캔들을 수정('updateCandle')해야 할지 아니면 새로운 캔들을 생성('fillCandle')해야 할지 판단합니다.
그런데, 다수의 종목에 대해 캔들정보의 상태를 유지하면서 독립적인 실행이 가능하도록 해야 하므로 클래스를 사용하는 것이 바람직할 듯 합니다. 종목코드와 종목명(실제로는 시트명)을 멤버(member)로 하는 클래스 모듈을 생성하여 기존의 'storeCandleInfo'와 'fillCandle' 및 'updateCandle'을 클래스의 멤버 함수로 이동하도록 하겠습니다.
아래 그림과 같이 VBAProject 트리목록에서 작업 중인 파일의 Context 메뉴 → '삽입' → '클래스 모듈'을 선택하여 클래스 모듈을 생성합니다.
그림 10. 클래스 모듈 추가하기 |
그림 11과 같이 클래스 이름을 'CCandleFeeder'로 수정하고, 아래 클래스 소스를 복사하여 입력합니다.
그림 11. 'CClassFeeder' 클래스 소스 입력 |
※ (소스 - CCandleFeeder 클래스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private pStockCode As String
Private pCandleSheet As String
Private pDDEsheet As String
Property Let StockCode(v As String)
If v = Empty Or v = "" Then _
Err.Raise 8901, "CCandleFeeder.Let StockCode", "Invalid parameter"
pStockCode = v
End Property
Property Let CandleSheet(v As String)
If v = Empty Or v = "" Then _
Err.Raise 8901, "CCandleFeeder.Let CandleSheet", "Invalid parameter"
pCandleSheet = v
End Property
Property Let DDEsheet(v As String)
If v = Empty Or v = "" Then _
Err.Raise 8901, "CCandleFeeder.Let DDEsheet", "Invalid parameter"
pDDEsheet = v
End Property
Sub store(s_code)
Static dde_cell As Integer 'the row of DDE cells of selected stock
Static prev_dt As String 'previous date
Static prev_tu As Integer 'previous time unit
Static prev_li As Integer 'row number of the row being updated
Static prev_vo As Double 'sum of volumes of prev candles of the same day
Static c_p_hr As Integer 'the number of candles per hour
On Error GoTo EH_storeCandleInfo:
If s_code = pStockCode Then
sh_name = pCandleSheet
Debug.Print "sh_name " & sh_name
If dde_cell = 0 Then dde_cell = rowSearch(pDDEsheet, 2, "'" & s_code & "'")
If c_p_hr = 0 Then c_p_hr = candlesPerHour(sh_name)
'Debug.Print "c_p_hr " & c_p_hr
With Sheets(sh_name)
'Debug.Print "prev_li " & prev_li
If prev_li = 0 Then
If .Cells(2, 1).Value = "" Then
prev_li = 1
Else
'prev_li = .UsedRange.Cells(.Cells.Count).Row '===> Overflow
prev_li = .Cells(1, 1).End(xlDown).Row
End If
End If
'Debug.Print "prev_li " & prev_li
cp = Sheets(pDDEsheet).Cells(dde_cell, 2).Value '현재가
cv = Sheets(pDDEsheet).Cells(dde_cell, 3).Value '거래량
dv = Abs(Sheets(pDDEsheet).Cells(dde_cell, 4).Value) '체결량
bt = Sheets(pDDEsheet).Cells(dde_cell, 5).Value '체결시간
If prev_li = 1 Then
If c_p_hr = -1 Then GoTo DailyNewCandle:
NewCandle:
prev_tu = WorksheetFunction.Floor(TimeValue(Right(bt, 8)) _
* 24 * c_p_hr, 1)
Debug.Print "prev_tu " & prev_tu
HourlyNewCandle:
Dim hr As Integer, hrstr As String
hr = WorksheetFunction.Floor(prev_tu / c_p_hr, 1) 'prev_tu / c_p_hr
hrstr = hr
If hr < 10 Then hrstr = "0" & hr
If c_p_hr > 1 Then
mt = (prev_tu Mod c_p_hr) * (60 / c_p_hr)
If Len(mt) = 1 Then mt = "0" & mt
hr_mt = "-" & hrstr & ":" & mt & ":00"
ElseIf c_p_hr > 0 Then
'hr_mt = "-" & prev_tu & ":00:00"
hr_mt = "-" & hrstr & ":00:00" ' ◀ 버그 수정
End If
DailyNewCandle:
prev_dt = Replace(Format(Date, "yyyy/mm/dd"), "-", "/")
Debug.Print "prev_dt & hr_mt " & prev_dt & hr_mt
prev_li = prev_li + 1
Call fillCandle(sh_name, prev_li, prev_dt & hr_mt, cp, cv - prev_vo) 'dv)
Exit Sub
End If
If prev_dt = "" Then prev_dt = Left(.Cells(prev_li, 1).Value, 10)
If c_p_hr = -1 Then
If DateValue(prev_dt) <> Date Then
GoTo DailyNewCandle:
Else
Call updateCandle(sh_name, prev_li, cp, cv)
End If
Else
If prev_tu = 0 Then
Debug.Print "prev_li " & prev_li
Debug.Print Right(.Cells(prev_li, 1).Value, 8)
prev_tu = WorksheetFunction.Floor(TimeValue( _
Right(.Cells(prev_li, 1).Value, 8) _
) * 24 * c_p_hr, 1)
End If
If DateValue(prev_dt) <> Date Then
Debug.Print "Going to NewCandle"
GoTo NewCandle:
Else
cur_tu = WorksheetFunction.Floor(TimeValue(Right(bt, 8)) _
* 24 * c_p_hr, 1)
If prev_tu <> cur_tu Then
Debug.Print Right(bt, 8)
Debug.Print prev_tu & "," & cur_tu
prev_vo = cv - dv
prev_tu = cur_tu
GoTo HourlyNewCandle:
Else
If prev_vo = 0 Then prev_vo = cv - dv - .Cells(prev_li, 6).Value
Call updateCandle(sh_name, prev_li, cp, cv - prev_vo)
End If
End If
End If
End With
End If
Exit Sub
EH_storeCandleInfo:
'MsgBox "Error(" & Err.Number & ") " & Err.Description & " [storeCandleInfo]"
Err.Raise Err.Number, "storeCandleInfo", Err.Description
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function fillCandle(sh_name, l, d, p, v)
With Sheets(sh_name)
.Cells(l, 1).Value = d
.Cells(l, 2).Value = p
.Cells(l, 3).Value = p
.Cells(l, 4).Value = p
.Cells(l, 5).Value = p
.Cells(l, 6).Value = v
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function updateCandle(sh_name, l, p, v)
With Sheets(sh_name)
If p > .Cells(l, 3).Value Then .Cells(l, 3).Value = p
If p < .Cells(l, 4).Value Then .Cells(l, 4).Value = p
.Cells(l, 5).Value = p
.Cells(l, 6).Value = v
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
☞ 클래스는 종목코드와 시트명(종목명), DDE 시트명을 멤버로 갖으며, 기존 'storeCandleInfo' Sub모듈을 'CCandleFeeder.store'로 활용합니다. 마찬가지로 'fillCandle'과 'updateCandle'도 클래스의 멤버 함수가 됩니다.
Private pStockCode As String
Private pCandleSheet As String
Private pDDEsheet As String
Property Let StockCode(v As String)
If v = Empty Or v = "" Then _
Err.Raise 8901, "CCandleFeeder.Let StockCode", "Invalid parameter"
pStockCode = v
End Property
Property Let CandleSheet(v As String)
If v = Empty Or v = "" Then _
Err.Raise 8901, "CCandleFeeder.Let CandleSheet", "Invalid parameter"
pCandleSheet = v
End Property
Property Let DDEsheet(v As String)
If v = Empty Or v = "" Then _
Err.Raise 8901, "CCandleFeeder.Let DDEsheet", "Invalid parameter"
pDDEsheet = v
End Property
Sub store(s_code)
Static dde_cell As Integer 'the row of DDE cells of selected stock
Static prev_dt As String 'previous date
Static prev_tu As Integer 'previous time unit
Static prev_li As Integer 'row number of the row being updated
Static prev_vo As Double 'sum of volumes of prev candles of the same day
Static c_p_hr As Integer 'the number of candles per hour
On Error GoTo EH_storeCandleInfo:
If s_code = pStockCode Then
sh_name = pCandleSheet
Debug.Print "sh_name " & sh_name
If dde_cell = 0 Then dde_cell = rowSearch(pDDEsheet, 2, "'" & s_code & "'")
If c_p_hr = 0 Then c_p_hr = candlesPerHour(sh_name)
'Debug.Print "c_p_hr " & c_p_hr
With Sheets(sh_name)
'Debug.Print "prev_li " & prev_li
If prev_li = 0 Then
If .Cells(2, 1).Value = "" Then
prev_li = 1
Else
'prev_li = .UsedRange.Cells(.Cells.Count).Row '===> Overflow
prev_li = .Cells(1, 1).End(xlDown).Row
End If
End If
'Debug.Print "prev_li " & prev_li
cp = Sheets(pDDEsheet).Cells(dde_cell, 2).Value '현재가
cv = Sheets(pDDEsheet).Cells(dde_cell, 3).Value '거래량
dv = Abs(Sheets(pDDEsheet).Cells(dde_cell, 4).Value) '체결량
bt = Sheets(pDDEsheet).Cells(dde_cell, 5).Value '체결시간
If prev_li = 1 Then
If c_p_hr = -1 Then GoTo DailyNewCandle:
NewCandle:
prev_tu = WorksheetFunction.Floor(TimeValue(Right(bt, 8)) _
* 24 * c_p_hr, 1)
Debug.Print "prev_tu " & prev_tu
HourlyNewCandle:
Dim hr As Integer, hrstr As String
hr = WorksheetFunction.Floor(prev_tu / c_p_hr, 1) 'prev_tu / c_p_hr
hrstr = hr
If hr < 10 Then hrstr = "0" & hr
If c_p_hr > 1 Then
mt = (prev_tu Mod c_p_hr) * (60 / c_p_hr)
If Len(mt) = 1 Then mt = "0" & mt
hr_mt = "-" & hrstr & ":" & mt & ":00"
ElseIf c_p_hr > 0 Then
'hr_mt = "-" & prev_tu & ":00:00"
hr_mt = "-" & hrstr & ":00:00" ' ◀ 버그 수정
End If
DailyNewCandle:
prev_dt = Replace(Format(Date, "yyyy/mm/dd"), "-", "/")
Debug.Print "prev_dt & hr_mt " & prev_dt & hr_mt
prev_li = prev_li + 1
Call fillCandle(sh_name, prev_li, prev_dt & hr_mt, cp, cv - prev_vo) 'dv)
Exit Sub
End If
If prev_dt = "" Then prev_dt = Left(.Cells(prev_li, 1).Value, 10)
If c_p_hr = -1 Then
If DateValue(prev_dt) <> Date Then
GoTo DailyNewCandle:
Else
Call updateCandle(sh_name, prev_li, cp, cv)
End If
Else
If prev_tu = 0 Then
Debug.Print "prev_li " & prev_li
Debug.Print Right(.Cells(prev_li, 1).Value, 8)
prev_tu = WorksheetFunction.Floor(TimeValue( _
Right(.Cells(prev_li, 1).Value, 8) _
) * 24 * c_p_hr, 1)
End If
If DateValue(prev_dt) <> Date Then
Debug.Print "Going to NewCandle"
GoTo NewCandle:
Else
cur_tu = WorksheetFunction.Floor(TimeValue(Right(bt, 8)) _
* 24 * c_p_hr, 1)
If prev_tu <> cur_tu Then
Debug.Print Right(bt, 8)
Debug.Print prev_tu & "," & cur_tu
prev_vo = cv - dv
prev_tu = cur_tu
GoTo HourlyNewCandle:
Else
If prev_vo = 0 Then prev_vo = cv - dv - .Cells(prev_li, 6).Value
Call updateCandle(sh_name, prev_li, cp, cv - prev_vo)
End If
End If
End If
End With
End If
Exit Sub
EH_storeCandleInfo:
'MsgBox "Error(" & Err.Number & ") " & Err.Description & " [storeCandleInfo]"
Err.Raise Err.Number, "storeCandleInfo", Err.Description
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function fillCandle(sh_name, l, d, p, v)
With Sheets(sh_name)
.Cells(l, 1).Value = d
.Cells(l, 2).Value = p
.Cells(l, 3).Value = p
.Cells(l, 4).Value = p
.Cells(l, 5).Value = p
.Cells(l, 6).Value = v
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function updateCandle(sh_name, l, p, v)
With Sheets(sh_name)
If p > .Cells(l, 3).Value Then .Cells(l, 3).Value = p
If p < .Cells(l, 4).Value Then .Cells(l, 4).Value = p
.Cells(l, 5).Value = p
.Cells(l, 6).Value = v
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
☞ 클래스는 종목코드와 시트명(종목명), DDE 시트명을 멤버로 갖으며, 기존 'storeCandleInfo' Sub모듈을 'CCandleFeeder.store'로 활용합니다. 마찬가지로 'fillCandle'과 'updateCandle'도 클래스의 멤버 함수가 됩니다.
기존 'storeCandleInfo'는 선택된 각각의 종목에 대응하는 CCandleFeeder 오브젝트를 생성하여 오브젝트의 멤버 함수인 'store' Sub모듈을 호출하도록 수정합니다. 아래 소스로 대체하십시요.
그림 12. 'storeCandleInfo' 수정 |
※ (소스 - 변경 후 'storeCandleInfo')
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub storeCandleInfo(s_code)
Static feeders As Collection
'On Error GoTo EH_storeCandleInfo:
If feeders Is Nothing Then Set feeders = New Collection
For i = LBound(ss_list) To UBound(ss_list)
Dim cfeeder As CCandleFeeder
On Error Resume Next
Set cfeeder = feeders.Item(ss_list(i, 1))
If Err.Number = 5 Then GoTo RegisterFeeder:
On Error GoTo 0
If Not cfeeder Is Nothing Then GoTo DoFeed:
On Error GoTo EH_storeCandleInfo:
RegisterFeeder:
Set cfeeder = New CCandleFeeder
cfeeder.StockCode = ss_list(i, 1)
cfeeder.CandleSheet = ss_list(i, 2)
cfeeder.DDEsheet = dde_sheet
feeders.Add cfeeder, ss_list(i, 1)
DoFeed:
cfeeder.store (s_code)
NextLoop:
Next i
Exit Sub
EH_storeCandleInfo:
If Err.Number = 8901 Then
Debug.Print "Error(8901) " & Err.Source
Err.Clear
GoTo NextLoop:
End If
MsgBox "Error(" & Err.Number & ") " & Err.Description & " [storeCandleInfo]"
Err.Raise Err.Number, "storeCandleInfo", Err.Description
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
☞ 종목별로 'CCandleFeeder' 오브젝트를 하나씩 할당하고 DDE 셀 변화에 따라 해당하는 종목의 오브젝트를 찾아(그림 12의 1과 2) 'store'를 호출(그림 12의 3)합니다.
모든 작업이 끝났습니다. 문서를 저장하고, 영웅문의 DDE 서비스가 활성화 상태인지 확인하신 후 테스트해 보시기 바랍니다.
Sub storeCandleInfo(s_code)
Static feeders As Collection
'On Error GoTo EH_storeCandleInfo:
If feeders Is Nothing Then Set feeders = New Collection
For i = LBound(ss_list) To UBound(ss_list)
Dim cfeeder As CCandleFeeder
On Error Resume Next
Set cfeeder = feeders.Item(ss_list(i, 1))
If Err.Number = 5 Then GoTo RegisterFeeder:
On Error GoTo 0
If Not cfeeder Is Nothing Then GoTo DoFeed:
On Error GoTo EH_storeCandleInfo:
RegisterFeeder:
Set cfeeder = New CCandleFeeder
cfeeder.StockCode = ss_list(i, 1)
cfeeder.CandleSheet = ss_list(i, 2)
cfeeder.DDEsheet = dde_sheet
feeders.Add cfeeder, ss_list(i, 1)
DoFeed:
cfeeder.store (s_code)
NextLoop:
Next i
Exit Sub
EH_storeCandleInfo:
If Err.Number = 8901 Then
Debug.Print "Error(8901) " & Err.Source
Err.Clear
GoTo NextLoop:
End If
MsgBox "Error(" & Err.Number & ") " & Err.Description & " [storeCandleInfo]"
Err.Raise Err.Number, "storeCandleInfo", Err.Description
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
☞ 종목별로 'CCandleFeeder' 오브젝트를 하나씩 할당하고 DDE 셀 변화에 따라 해당하는 종목의 오브젝트를 찾아(그림 12의 1과 2) 'store'를 호출(그림 12의 3)합니다.
모든 작업이 끝났습니다. 문서를 저장하고, 영웅문의 DDE 서비스가 활성화 상태인지 확인하신 후 테스트해 보시기 바랍니다.
그림 13. 테스트 결과 |
CCandleFeeder의 60분 캔들정보 축적에 버그가 있었습니다. 수정했습니다.
답글삭제질문드립니다.
답글삭제분봉말고 일봉의 데이터를 축적하고싶은데
현재가가 맨위로 가게축적할수는 없나요??
EX) 6/24 1000원
6/23 2000원
.
.
일화이백님. 소중한 정보 공유해 주셔서 정말 감사합니다. 일화이백님의 자료로 정상 작동 되었습니다. 그동안 고민하던것이 풀렸네요...^^
답글삭제그런데, 5분봉을 1분봉으로 바꾸려고
candlesperhour = 60으로 바꾸고, storeCandleinfo , Newcandle, HourlyNewCandle, DailyNewCandle,쭉 읽어보고 연관성 있는 숫자를 바꿔서 해봤는데, 뭐가 또 잘못 됐나봐요..
작동을 안해요...헐~~
변경 소스좀 알려 주시면 안될까요?
1분봉 기능을 추가하려면, candlesPerHour 함수와 봉 주기 선택 폼 관련 소스 등 여러 군데 수정해야 할 것입니다. 우선 candlesPerHour 함수는 아래와 같이 수정하면 되겠지요.
삭제Function candlesPerHour(sh_name) As Integer
If InStr(sh_name, "-일") > 1 Then
candlesPerHour = -1
ElseIf InStr(sh_name, "-60분") > 1 Then
candlesPerHour = 1
ElseIf InStr(sh_name, "-15분") > 1 Then
candlesPerHour = 4
ElseIf InStr(sh_name, "-5분") > 1 Then
candlesPerHour = 12
Else
candlesPerHour = 60
End If
End Function
1분봉 시트가 생성되도록 하셨다면, candlesPerHour가 60으로 제대로 넘어오는지 디버깅해 보시기 바랍니다.
좀더 공부 해야 될듯 합니다. candlesPerHour넘어오지 않습니다. 콤보박스도 손봐야 할것 같습니다.
삭제storeCandleinfo , Newcandle, HourlyNewCandle, DailyNewCandle 소스들의 의미를 먼저 알아야 손볼수 있을것 같아요...근데, 함수에 대한 자세한 설명들이 없어서, 어디가서 찾아봐야 하나요????
신경써 주셔서 감사합니다.
Visual Basic language reference 참고하시기 바라구요, 시작이 반입니다.
삭제https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/visual-basic-language-reference
네 감사 합니다. 궁금한 사항 있으면 또 연락 드리겠습니다.
삭제안녕하세요. 일화이백님. 오랫만에 글을 남깁니다.
답글삭제현재 dde소스를 openapi로 전환 가능한가요? 가능하다면 어떻게 해야 되나요?
일화이백님이 제공해 주신 소스로 sheet에다가 수식을 걸어서 분석하다보니, 엑셀이 너무 무거워 졌어요
컴퓨터가 버벅거리고, dde데이타가 정확하지가 않더라구요.??
그래서 찾아 보다가, api는 정확하다는 정보를 보고 이렇게 글을 올립니다.
쉬트에 50개 정도의 종목을 분석 한다고 할때, api로 정보를 받는게 낳을까요? dde로 받는게 낳을까요?
혹시 버벅 거리는 문제점을 줄일수 있는 방법은 없을까요? (dde데이터가 슬러우버젼으로 바낍니다)
견해를 조심스럽게 여쭈어 보네요...감사합니다.
안녕하세요?
삭제제 경험을 말씀드리자면, DDE로 데이터를 받아 엑셀 VBA로 작업하는 것과,
증권사에서 제공하는 API로 데이터를 받아 작업하는 것은,
어떤 로직을 어떻게 프로그래밍하여 무슨 결과(차트나 폼 UI 등)를 볼 수 있게끔 만드느냐에 따라 달라질텐데요,...
실시간 감시해야할 종목의 수가 많고, 다양한 조건에 알람이 울리게끔 하거나 화면 등 폼으로 출력해야할 정보가 많을 경우에는 API를 사용하여 코딩하는 것이 더 유용할 것이라고 말씀드립니다.
그런데,..., 증권사에서 제공하는 API를 활용하는 프로그래밍을 하더라도, 많은 종목의 스켈핑에는 적절하지 않을 수 있다는 말씀 또한 드립니다.
그리고, 엑셀 수식을 vba로 입력하면 버벅 거리는것이 좀 나아 질까요???
답글삭제작성자가 댓글을 삭제했습니다.
답글삭제API를 사용하더라도 종목 수의 제한은 있을 것입니다.
삭제키움이던, 제가 많이 활용하는 대신증권 API이던 제한 없이 사용하는 것은 기대하지 않으시는게 좋습니다.
답변 감사합니다. 경험이 없어서, 인터넷에 나와 있는 정보만으로는 판단할수가 없었습니다.
삭제조금이나마, 포기를 빨리 하고 더 좋은 방법에 아이디어로 고민 해 보겠습니다. 감사합니다.
50개종목 dde 데이터 수신받을때는 괜찮은데, Scheduling을 돌리면 속도가 슬러우버젼으로 바뀌는것 같아요. 속도 향상 할수 있는 방법이 없을까요?
답글삭제dde데이타 연결 수식을 vba로 입력하면 버벅 거리는것이 좀 나아질까요?
키움 조회제한에 대해서 제약사항이 있던데, 1초에 5회로 api로 활용이 가능 할까요???
이리저리 고민하다고, 질문만 올리네요....감사합니다.
엑셀 DDE를 활용하는 경우에는 (1) DDE나 (2) VBA 코어 자체의 성능을 개발자가 직접 control하지 못합니다.
삭제증권사 API의 경우는 (2)의 제약이 거의 없다고 생각하면 될 듯합니다.
DDE 보다는 증권사 API 기반 프로그래밍이 성능 측면에선 더 나을 것입니다.
물론, 증권사마다 API가 다르므로, 범용성을 고려한다면 DDE가 API에 비해 장점이 있을 것이구요.
API 기반으로 실시간 Tick 데이터를 받는 것은 아마도 종목 수에 제약이 없을 것입니다. DDE가 그렇듯이요.
하지만, 차트 데이터와 같이 사이즈가 큰 데이터를 받는 것은 '1초에 5회'와 같은 제한이 당연하겠지요.
증권사 서버에 부담이 클테니까요.
이런 제약으로부터 벗어나려면,... 차트 데이터들은 '가령 어제까지의 데이터는 내가 쌓아둔 것을 읽어들이고, 오늘 장 시작 이후의 틱 데이터를 받아가며 차트를 그린다'라는 식의 프로그래밍을 해야합니다.
다른 생각들이 많이 스쳐가네요. 프로그램을 못만지니 계속 꼼수만 늘어가네요...인터넷으로 전문가들 소스를 가지고 이리저리 적용 하다보니, 원하는것 하나 얻을 때마다 엄첨 힘이 드네요...
답글삭제답변 정말 감사합니다.
혹시, 메일로 문의 같은거 드려도 될까요??? 가능하시면 제 메일 주소로 회신 부탁 드립니다.
myeongjae1005@nate.com 감사합니다.