2013년 2월 2일 토요일

Excel에서 HTS DDE 활용하기 5 - Chart와 지표 II

※ Elliott Pattern Helper Add In
  • Download Add In for Excel 2007 
  • Download Add In for Excel 2003 


  지난 글에서는 단순한 캔들 차트를 출력해 보았습니다. 그림 1이 그것인데요, '단순'한 차트지만 수작업이 아닌 VBA 프로그래밍으로 구현한 것입니다.
   

그림 1. 캔들 차트

  그런데, 작은 차트 속에 캔들이 너무 많이 있어 보기가 쉽지 않습니다. 캔들 수를 줄여서 보기 쉽게 하는 대신 스크롤(scroll) 기능을 구현해서 전체 데이터를 볼 수 있도록 한다면 좋지 않을까요?

  이 글의 주제는 '스크롤을 할 수 있는 엑셀 차트'입니다. 우선, 엑셀이 제공하는 스크롤 바 콘트롤의 원리를 보겠습니다.

  새 시트를 생성한 후 '개발 도구' → '삽입' → '스크롤 막대'를 선택하고 적당한 크기로 스크롤 바를 만듭니다(그림 2, 3).
  
그림 2. 스크롤 바 추가

그림 3. 컨트롤 서식
   생성한 스크롤 바의 Context Menu → '컨트롤 서식'을 선택합니다.

그림 4. '컨트롤 서식' 선택

  그림 5에서 볼 수 있듯이 스크롤 바는 속성 중의 하나로 '연결 셀(LinkedCell)'을 지정해야 합니다. H1셀로 지정합니다.'현재값', '최소값', '최대값', '증분 변경' 등의 속성은 기본값으로 둡니다.

그림 5.

  셀 A2에서 F2까지 적당히 입력한 후, 셀 A3에 다음 그림과 같이 수식을 입력합니다.

그림 6-1. 수식 입력 1

  OFFSET('기아차-5분'!A2,Sheet6!H1,0)은 '기아차-5분' 시트의 셀 A2에서 아래로  'Sheet6!H1' 값만큼 이동한 셀의 값입니다. 위 수식은 셀 A3에 OFFSET('기아차-5분'!A2,Sheet6!H1,0)의 값이 null string이 아닐 경우, 그 값으로 채우는 것입니다.

  이와 유사하게 셀 B3에서 F3까지 아래 수식으로 채웁니다.

그림 6-2, 수식 입력 2

  범위 (A3:F3)를 선택하고 자동채우기(AutoFill) 기능을 사용하여 10줄 정도를 채운 뒤 다음 그림과 같이 OFFSET 함수의 두 번째 인자가 H1이 되도록 합니다.


그림 6-3. 수식 입력 3

  모두 채워 넣었으면 스크롤 바를 움직여볼까요?

그림 7. 스크롤 바 테스트

  그림 7과 같이 스크롤을 함에 따라 셀 A3에서 F12까지의 값이 변하는 것을 확인할 수 있습니다. 엑셀의 기본 기능을 사용하여 차트를 하나 만들어 보겠습니다. 다음 그림 8과 같이 종가 열까지만 해서 범위 (A2:E12)를 선택한 후 '삽입' → '차트' → '주식형(시가-고가-저가-종가)'을 선택하여 캔들 차트를 생성합니다.

그림 8. 차트 생성

  차트를 적절히 위치시킨 후 스크롤 바의 좌우 화살표 버튼을 클릭해 보면 차트가 스크롤 되는 효과를 확인할 수 있습니다.

그림 9. 차트 스크롤 효과

  이제는 그림 9까지 했던 작업을 VBA로 프로그래밍하여 버튼 클릭만으로도 동일한 작동을 하도록 구현하겠습니다.

  우선, 시나리오를 정리하겠습니다.
  • 시트 위에서 종목명을 선택하고 'Chart' 버튼을 클릭하면 콤보 박스와 버튼을 포함하는 차트 출력 시트(시트명 'CHARTS')가 생성되면서 선택된 종목의 캔들 차트 하나가 출력된다.
  • 콤보 박스는 기존에 생성된 캔들정보 시트(*-5분, *-15분, *-60분, *-일) 메뉴를 포함한다. 메뉴에는 선택된 종목의 시트만 포함된다.
  • 메뉴를 선택하고 버튼을 클릭하면 차트는 선택된 메뉴의 캔들 차트로 전환된다('one chart with multiple pages').
  • 차트는 스크롤 기능을 갖는다.
  먼저 차트 출력 시트를 생성하는 함수 'createChartSheet'를 작성합니다. 시트위에서도 언급했듯이 시트는 메뉴를 선택하기 위한 콤보 박스와 차트 전환을 위한 버튼을 포함해야 합니다. 함수의 인자는 가변 길이의 ParamArray로 넘기는데, 최소한 다음을 포함합니다.
  • 시트명, 메뉴 목록(comma-separated string), 차트 유형, 차트 크기(width, height, length) 
  ※ (함수 'createChartSheet' 소스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function createChartSheet(ParamArray varArgs() As Variant) As Boolean
    If UBound(varArgs) < 5 Then _
        Err.Raise 8902, "createChartSheet", "ParamArray too short"
       
    Dim choices As String
    createChartSheet = False
   
    On Error GoTo EH_createChartSheet:
    csname = varArgs(0)
    If Not sheetExist(csname) Then
        Set tmpWS = Worksheets.Add(After:=Worksheets(Worksheets.Count))
        tmpWS.Name = csname
       
        If UBound(varArgs) < 1 Then GoTo SkipControl:
        If varArgs(1) = xlNullString Then GoTo SkipControl:
        tmpWS.Cells(2, 1).Value = "Charts:"
        tmpWS.Cells(2, 2).Font.Size = 10
        tmpWS.Columns(2).ColumnWidth = tmpWS.Columns(1).ColumnWidth * 1.8
        tmpWS.Columns(3).ColumnWidth = tmpWS.Columns(1).ColumnWidth * 0.2
       
        Set btn = tmpWS.Buttons.Add(tmpWS.Cells(2, 4).Width * 3, _
                                    tmpWS.Cells(2, 4).Height, _
                                    tmpWS.Cells(2, 4).Width, _
                                    tmpWS.Cells(2, 4).Height)
        btn.Name = "ButtonChange"
        btn.OnAction = "'onChartChange " & varArgs(2) & ", " _
                       & varArgs(3) & ", " & varArgs(4) & ", " & varArgs(5) & "'"
        btn.Characters.Text = "Change"
        With btn.Characters.Font
        '    .Name = "Times New Roman"
        '    .FontStyle = "Regular"
            .Size = 10
        End With
   
    Else
        Set tmpWS = sheets(csname)
        On Error Resume Next
        For Each cht In tmpWS.ChartObjects
            cht.Delete
        Next cht
        For Each bar In tmpWS.ScrollBars
            bar.Delete
        Next bar
        On Error GoTo 0
    End If
   
    If UBound(varArgs) < 1 Then GoTo SkipControl:
    With tmpWS.Cells(2, 2).Validation
        .Delete
        .Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
             Operator:=xlBetween, Formula1:=varArgs(1)
        .IgnoreBlank = True
        .InCellDropdown = True
        .InputTitle = ""
        .ErrorTitle = ""
        .InputMessage = ""
        .ErrorMessage = ""
        .ShowInput = True
        .ShowError = True
    End With
    tmpWS.Cells(2, 2).Font.Size = 9
       
SkipControl:
    createChartSheet = True
   
MyExit:
    Exit Function
   
EH_createChartSheet:
    MsgBox "Error[" & Err.Number & "] " & Err.Description & " [createChartSheet]"
    GoTo MyExit:
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ☞ 위 소스는
크게 세 부분으로 구성됩니다. 차트 출력 시트를 생성하는 부분, 버튼을 생성하고 이벤트 처리 매크로를 할당하는 부분, 그리고 메뉴 선택을 위한 부분입니다.
  ☞ 메뉴 선택을 위한 콤보 박스는 UI 콘트롤이 아닌 셀 Validation 기능을 사용했습니다.
  다음은 차트 출력 시트의 버튼 이벤트 처리 매크로 'onChartChange'를 작성하겠습니다. 위 'createChartSheet' 소스를 자세히 보면, 'onChartChange'의 인자가 네 개임을 알 수 있습니다. 하지만 출력할 캔들정보 시트를 알려주어야 할 필요도 있으므로 ParamArray로 넘기도록 하겠습니다.
  • 차트 유형, 차트 크기(width, height, length), 캔들정보 시트명
  ※ ('onChartChange' 소스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub onChartChange(ParamArray varArgs() As Variant)
    If UBound(varArgs) < 3 Then _
        Err.Raise 8902, "onChartChange", "ParamArray too short"
       
    Dim chtRng As String, sn As String
    On Error GoTo EH_onChartChange:
   
    sn = Cells(2, 2).Value
    If UBound(varArgs) > 3 Then sn = varArgs(4)
    If sn = xlNullString Then Exit Sub
   
    csheet = ActiveSheet.Name
    chtRng = fillChartRng(csheet & "_AUX", sn, True, 2, varArgs(3))
    sheets(csheet).Select
   
    endRow = sheets(sn).Cells(1, 1).End(xlDown).Row
    If ActiveSheet.ChartObjects.Count = 0 Then
        drawChartTWH sheets(csheet & "_AUX").Range(chtRng), _
                     varArgs(0), varArgs(1), varArgs(2), csheet, _
                     csheet & "_AUX" & "!" & Cells(1, 1).Address, _
                     WorksheetFunction.Max(endRow - varArgs(3) - 1, 0)
    Else
        Dim ch As ChartObject, sbar As ScrollBar
        Set ch = sheets(csheet).ChartObjects(1)
        ch.Chart.SetSourceData Source:=sheets(csheet & "_AUX").Range(chtRng)
        Set sbar = sheets(csheet).ScrollBars(1)
        sbar.Max = WorksheetFunction.Max(endRow - varArgs(3) - 1, 0)
        sbar.Value = WorksheetFunction.Max(endRow - varArgs(3) - 1, 0)
    End If
   
    Exit Sub

EH_onChartChange:
    MsgBox "Error(" & Err.Number & ") " & Err.Description & " [onChartChange]"
End Sub
 
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ☞ 위 소스는 선택된 캔들정보 시트로부터 차트의 데이터 소스 범위를 구성하는 부분, 차트를 생성/전환하는 부분으로 구성됩니다.
  ☞ 차트의 데이터 소스 범위 구성은 'fillChartRng' 함수가 수행하며, 그림 6-1부터 6-3까지의 과정을 자동화하는 것입니다.
  ☞ 차트 생성은 'drawChartTWH' 함수가 수행하며, 차트 전환은 이미 생성된 차트의 데이터 소스를 변경하는 것입니다.
  차트의 데이터 소스 범위는 고정시키고, 셀 함수 OFFSET을 활용하여 스크롤 기능을 구현하는 과정을 위에서 설명했습니다. 이를 위해 지정한 범위에 셀 수식을 자동으로 채우는 함수 'fillChartRng'를 작성하겠습니다. 필요한 인자는 다음과 같습니다.
  • 차트의 데이터 소스를 포함하는 시트명, 캔들정보 시트명, 스크롤 바의 LinkedCell 식별을 위한 column number, 차트의 length
  ※ (함수 'fillChartRng' 소스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function fillChartRng(ParamArray varArgs() As Variant) As String
    If UBound(varArgs) < 4 Then _
        Err.Raise 8902, "fillChartRng", "ParamArray too short"
   
    dsheet = varArgs(0)     'sheet to which the chart pointing
    If dsheet = xlNullString Then _
        Err.Raise 8902, "fillChartRng", "Sheet name null"
       
    osheet = varArgs(1)     'sheet including original data of the chart
                            'if xlNullString, = dsheet
    If osheet = xlNullString Then osheet = dsheet
    If Not sheetExist(osheet) Then _
        Err.Raise 8902, "fillChartRng", "Data sheet not exist"
   
    On Error GoTo EH_fillChartRng:
    If Not sheetExist(dsheet) Then
        Set tmpWS = Worksheets.Add(After:=Worksheets(Worksheets.Count))
        tmpWS.Name = dsheet
        If varArgs(2) Then tmpWS.Visible = xlSheetHidden
    Else
        Set tmpWS = sheets(dsheet)
        If varArgs(3) < 3 Then tmpWS.Cells.Clear    'dsheet contains data
    End If
   
    k = varArgs(3) - 1
    With sheets(dsheet)
        For j = 2 To 6
        .Cells(1, j).Value = sheets(osheet).Cells(1, j - 1).Value
        .Cells(2, j).FormulaR1C1 = "=IF(OFFSET('" & osheet & "'!RC[" & (-1) & _
                                   "],R1C" & k & ",0)<>"""",OFFSET('" & osheet & _
                                   "'!RC[" & (-1) & "],R1C" & k & ",0),"""")"
        Next j
       
        endRow = sheets(osheet).Cells(1, 1).End(xlDown).Row
        endRow = WorksheetFunction.Min(endRow, varArgs(4) + 1)
        .Cells(2, 2).Resize(1, 5).AutoFill _
                            Destination:=.Cells(2, 2).Resize(endRow, 5), _
                            Type:=xlFillDefault
    End With
    fillChartRng = Cells(1, 2).Address + ":" + Cells(endRow, 6).Address
    Exit Function
   
EH_fillChartRng:
    MsgBox "Error(" & Err.Number & ") " & Err.Description & " (on fillChartRng)"
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
   ☞ 함수의 기능은 그림 6-1부터 6-3까지의 과정을 자동화하는 것입니다.
   ☞ 세 번째 인자는 차트의 데이터 소스 시트를 감추기 위한 boolean형 인자입니다. 네 번째 인자 varArgs(3)는 스크롤 바의 LinkedCell을 지정하기 위한 것입니다.

  신규로 작성해야 할 부분은 완료되었고, 스크롤 바를 추가하기 위해 'drawChartTWH'를 수정하겠습니다. 스크롤 바의 LinkedCell 위치와 초기값을 알려주는 인자를 추가로 넘겨주고, 이를 받아 스크롤 바를 추가하는 로직을 입력합니다.
  ※ (수정 후 'drawChartTWH' 소스)

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function drawChartTWH(ParamArray varArgs() As Variant)
    Dim cht As ChartObject
    Dim chtSheet As Worksheet
   
    Set chtSheet = ActiveSheet
    If UBound(varArgs) > 3 Then
        If varArgs(4) = xlNullString Then GoTo UseActiveSheet:
        If Not sheetExist(varArgs(4)) Then
            Set chtSheet = Worksheets.Add(After:=Worksheets(Worksheets.Count))
            chtSheet.Name = varArgs(4)
        Else
            Set chtSheet = sheets(varArgs(4))
        End If
UseActiveSheet:
    End If

    po_y = chtSheet.ChartObjects.Count * (2 * Rows("1:1").RowHeight + varArgs(3)) _
           + 2 * Rows("1:1").RowHeight
    Set cht = chtSheet.ChartObjects.Add(Left:=0, Width:=varArgs(2), _
                                        Top:=po_y + Rows("1:1").RowHeight, _
                                        Height:=varArgs(3))
   
    cht.Chart.SetSourceData Source:=varArgs(0)
    cht.Chart.ChartType = varArgs(1)
   
    If UBound(varArgs) > 4 Then
        If varArgs(5) <> xlNullString Then
        With chtSheet.ScrollBars.Add(0, po_y, varArgs(2), Rows("1:1").RowHeight)
            '.Name = "ChartScroll"
            .Min = 0
            .Max = varArgs(6)
            .Value = varArgs(6)
            .SmallChange = 1
            .LargeChange = 1
            .LinkedCell = varArgs(5)
            .Display3DShading = True
        End With
        End If
    End If
   
    Set chtSheet = Nothing
    Set cht = Nothing
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ☞ 하이라이트된 부분이 추가된 소스입니다.

  마지막으로 추가 및 수정된 부분을 반영하기 위해 'doCharting' Sub모듈을 수정하도록 하겠습니다.
  ※ (수정 후 'doCharting' 소스)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub doCharting(sn)
    sn_cs = sheets_cs(sn)
    sn_Arr = Split(sn_cs, ",")
    If UBound(sn_Arr) < 0 Then Exit Sub
   
    If createChartSheet("CHARTS", sn_cs, xlStockOHLC, 600, 300, 140) Then
        sheets("CHARTS").Select
        Call onChartChange(xlStockOHLC, 600, 300, 140, sn_Arr(LBound(sn_Arr)))
    End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  추가된 모듈 및 함수 'createChartSheet', 'onChartChange', 'fillChartRng'를 기존의 'drawChartTWH' 함수 아래에 추가하고, 'drawChartTWH'와 'doCharting'은 위 소스를 참고하여 수정하거나 대체하십시요.

  작업 중인 파일을 저장한 후 테스트를 해 보겠습니다. ※ 테스트를 위해 5분, 15분, 일봉 데이터를 미리 충분하게 적재했습니다.

그림 10. 테스트 결과

  처음에 정했던 시나리오 대로 작동하는 것을 확인하셨나요?

  ※ 다음 글의 주제는 '차트와 DDE 연동'입니다.


댓글 없음:

댓글 쓰기