2013년 2월 21일 목요일

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

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

  지난 글에 이어 '시나리오 2'를 진행하겠습니다. 선택된 종목과 지표에 대해, 지표를 포함하는 캔들 차트를 출력할 수 있도록 할 것입니다.

▶ (시나리오 2). 'FChart' 수정  
    
  시나리오 2를 위한 폼 개체 편집을 시작합니다. 지난 과정에서 만들었던 폼 'FChart'의 빈 페이지를 활용하겠습니다. VBAProject 트리 목록에서 'FChart'의 Context 메뉴 → '개체 보기'를 선택한 후 'Page2'를 클릭합니다. 

  지표 선택을 위한 콤보상자를 만들겠습니다. 도구상자에서 콤보상자를 선택(그림 1의 1)하고, 폼 위에 적절히 위치시킨 후(그림 1의 2), 이름을 'ComboBoxTI'로 수정합니다(그림 1의 3). 레이블은 적당히 입력하십시요.

그림 1. 지표 선택을 위한 콤보상자 추가

  지표에 따라 설정해야할 기간(period)의 개수가 달라집니다. 시그널(지표의 이동평균)까지 최대 3 개가 필요하므로 텍스트 상자 세 개를 만들겠습니다.  

  도구상자에서 텍스트 상자를 선택(그림 2-1의 1)하고, 폼 위에 적절히 위치시킨 후(그림 2-1의 2), 텍스트 상자의 이름을 'TextBoxP1'으로 수정합니다(그림 2-1의 3).

그림 2-1. 기간 설정을 위한 텍스트 상자 추가 1
  
  동일한 방법으로 'TextBoxP2'와 'TextBoxP3'를 생성한 후 다음 그림 2-2와 같이 레이블을 적절히 입력합니다. ※ (주의) 입력한 이름이 정확한지 다시 한 번 확인하세요.
  
그림 2-2. 기간 설정을 위한 텍스트 상자 추가 2
  
  이동평균을 계산해야 하는 경우, 기본값으로 지수(exponential)이동평균을 사용하는데 이를 단순이동평균으로 변경할 때 사용할 체크박스(확인란)를 생성합니다.  


  도구상자에서 체크박스를 선택(그림 3의 1)하고, 폼 위에 적절히 위치시킨 후(그림 3의 2), 체크박스의 이름을 'CheckBoxSMA'로 수정합니다(그림 2-1의 3). Caption은 적절히 입력하십시요.

그림 3. 단순이동평균 지정을 위한 체크박스 추가

  페이지 이름 'Page2'를 적절히 수정하여 폼의 개체 편집을 완료합니다(그림 3의 4).

  다음은 위에서 생성한 페이지를 초기화하기 위한 코드를 입력하도록 하겠습니다. VBAProject 트리 목록에서 'FChart'의 Context 메뉴 → '코드 보기'를 선택하여 소스코드 편집 모드로 들어간 후 기존의 'UserForm_Initialize' 모듈을 아래와 같이 수정합니다.

  ※ (수정 후 'FChart' 초기화 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Private Sub UserForm_Initialize()
        TextBoxCW.Value = 550
        TextBoxCH.Value = 300
        TextBoxCL.Value = 80
       
        ComboBoxTI.AddItem "MACD", 0
        ComboBoxTI.AddItem "RSI", 1
        ComboBoxTI.AddItem "OBV", 2
       
        Call DisableAllP
    End Sub

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  ☞ 'DisableAllP'는 다음 소스를 참고하십시요.
  
  'ComboBoxTI'에서 선택된 지표에 따라 'TextBoxP1', 'TextBoxP2' 및 'TextBoxP3'의 초기화 값을 달리 설정하도록 합니다. VBAProject 트리 목록에서 'FChart'의 Context 메뉴 → '개체 보기'를 선택하여 폼 개체편집 모드로 들어간 후 콤보상자 'ComboBoxTI'를 더블클릭합니다. 소스코드 편집 모드로 바뀌면서 이벤트 처리를 위한 Sub모듈의 뼈대가 생성되는데 다음 소스를 참고하여 입력합니다.
  
  ※ ('ComboBoxTI_Change' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Private Sub ComboBoxTI_Change()
        Select Case ComboBoxTI.ListIndex
        Case 0
            Call EnableAllP
            TextBoxP1.Value = 5
            TextBoxP2.Value = 34
            TextBoxP3.Value = 5
        Case 1
            Call EnableP1
            TextBoxP1.Value = 13
        Case 2
            Call EnableP1
            TextBoxP1.Value = 9
        End Select
    End Sub

    Private Sub DisableAllP()
        TextBoxP1.Value = xlNullString
        TextBoxP2.Value = xlNullString
        TextBoxP3.Value = xlNullString
        TextBoxP1.Enabled = False
        TextBoxP2.Enabled = False
        TextBoxP3.Enabled = False
    End Sub

    Private Sub EnableAllP()
        TextBoxP1.Enabled = True
        TextBoxP2.Enabled = True
        TextBoxP3.Enabled = True
    End Sub

    Private Sub EnableP1()
        TextBoxP2.Value = xlNullString
        TextBoxP3.Value = xlNullString
        TextBoxP1.Enabled = True
        TextBoxP2.Enabled = False
        TextBoxP3.Enabled = False
    End Sub

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 
  그림 1~3 과정에서 만든 'ComboBoxTI'와 'TextBoxP1', 'TextBoxP2', 'TextBoxP3' 그리고 'CheckBoxSMA'에서 선택된 값들을 조합하여 'MACD (지수 5 34 5)'와 같은 형태의 formatted string을 만들기 위한 함수를 작성합니다. 아래 소스를 복사하여 입력하십시요.
  
  ※ (함수 'indicator_str' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Private Function indicator_str() As String
        Dim ns As String
        On Error GoTo Skip:
      
        Select Case ComboBoxTI.ListIndex
        Case 0
            ns = "MACD ("
            If TextBoxP1.Value = xlNullString Then GoTo Skip:
            If CInt(TextBoxP1.Value) > 0 Then
                ns = ns & TextBoxP1.Value & " "
            Else
                GoTo Skip:
            End If
            If CInt(TextBoxP2.Value) > 0 Then
                ns = ns & TextBoxP2.Value & " "
            Else
                GoTo Skip:
            End If
            If Not TextBoxP3.Value = xlNullString Then
                ns = ns & CInt(TextBoxP3.Value) & ")"
            Else
                ns = Left(ns, Len(ns) - 1)
                ns = ns & ")"
            End If
        Case 1
            ns = "RSI ("
            If CInt(TextBoxP1.Value) > 0 Then
                ns = ns & TextBoxP1.Value & ")"
            Else
                GoTo Skip:
            End If
        Case 2
            ns = "OBV"
            If Not TextBoxP1.Value = xlNullString Then
             ns = ns & " (" & CInt(TextBoxP1.Value) & ")"
            Else
                GoTo SkipMA:
            End If
        Case Else
            GoTo Skip:
        End Select
      
        If CheckBoxSMA.Value = True Then
            ns = Replace(ns, "(", "(단순 ")
        Else
            ns = Replace(ns, "(", "(지수 ")
        End If
      
    SkipMA:
        indicator_str = ns
    Skip:

    End Function

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

 
  폼 'FChart' 수정의 마지막 단계로, 'OK' 버튼 이벤트 매크로를 수정하겠습니다. 선택된 지표, 기간, 단순이동평균 여부를 'indicator_str' 함수를 사용하여 정해진 포맷으로 만든 후 'doChartging' 함수의 인자로 추가하여 호출합니다.
  
  ※ (수정 후 'ButtonOK_Click' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Private Sub ButtonOK_Click()
        Me.Hide
        sn = Selection.Cells(1, 1).Value
        If ActiveSheet.Name = CHART_POSTFIX Then
            If Cells(2, 2).Value <> xlNullString Then _
            sn = Left(Cells(2, 2).Value, InStr(Cells(2, 2).Value, "-") - 1)
        End If
        Call doCharting(sn, _
                        CInt(TextBoxCW.Value), _
                        CInt(TextBoxCH.Value), _
                        CInt(TextBoxCL.Value), _
                        indicator_str())
      
    End Sub

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

 
▶ (시나리오 2). Module 수정 
  

  폼 'FChart'의 'OK' 버튼 클릭 시 호출되는  'doCharting'을 수정합니다.  
  
  ※ (수정 후 'doCharting' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Sub doCharting(sn, cw, ch, cl, istr)
        Call prepareIndicators(istr, False)
        istr = Replace(istr, "(", "")
        istr = Replace(istr, ")", "")
        istr = Replace(istr, " ", "-")
      
        sn_cs = sheets_cs(sn)
        sn_Arr = Split(sn_cs, ",")
        If UBound(sn_Arr) < 0 Then Exit Sub
      
        If cw = Empty Or cw = xlNullString Or cw < 50 Then cw = CHART_W
        If ch = Empty Or ch = xlNullString Or ch < 50 Then ch = CHART_H
        If cl = Empty Or cl = xlNullString Or cw < 0 Then cl = CHART_L
      
        If createChartSheet(CHART_POSTFIX, sn_cs, xlStockOHLC, cw, ch, cl) Then
            sheets(CHART_POSTFIX).Select
            Call onChartChange(xlStockOHLC, cw, ch, cl, sn_Arr(LBound(sn_Arr)), istr)
        End If
    End Sub

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  ☞ 지난 글에서 'prepareIndicators' 함수를 작성했습니다. 호출하면서 두 번째 인자를 'False'로 넘겨주는데요, 이미 만들어진 'indicator_t' 구조체 배열('it_arr')에 해당 항목이 없을 경우에 추가하라는 의미입니다.
  ☞ 'onChartChange'를 호출할 때 지표 정보를 포함하는 인자를 추가로 넘겨줍니다.
  기존의 'onChartChange'는 ① 'fillChartRng' 함수를 호출하여 차트가 바라보는 데이터 범위-단일 Range 오브젝트를 선택된 종목의 데이터로 채우게 한 후, ② 'drawChartTWH'를 호출하여 차트를 출력하도록 합니다.

  이것을, 차트뿐만 아니라 지표까지 출력해야 하므로, 단일 Range가 아니라 두 개 또는 세 개의 Range에 대해서도 처리될 수 있도록 수정해야 합니다. ☞ 지표의 이동평균(Signal)까지 포함하는 경우 필요한 Range는 세 개 입니다.

  먼저 'fillChartRngMR' 함수를 신규로 작성하겠습니다.
  
  ※ (함수 'fillChartRngMR' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Function fillChartRngMR(ParamArray varArgs() As Variant) As Variant
        Dim rstr As String, cols As Variant, col_add As Integer
      
        If UBound(varArgs) < 4 Then _
            Err.Raise 8902, "
fillChartRngMR", "ParamArray too short"
      
        dsheet = varArgs(0)     'sheet to which the chart pointing
        If dsheet = xlNullString Then _
            Err.Raise 8902, "
fillChartRngMR", "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, "
fillChartRngMR", "Data sheet not exist"
      
        On Error GoTo EH_fillChartRngMR:
        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)
            endRow = sheets(osheet).Cells(1, 1).End(xlDown).Row
            .Cells(1, 1).value = WorksheetFunction.Max(endRow - varArgs(4) - 1, 0)
            endRow = WorksheetFunction.Min(endRow, varArgs(4) + 1)
          
            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
            rstr = Cells(2, 2).Address + ":" + Cells(endRow, 6).Address
          
            If UBound(varArgs) > 4 Then
                cols = getIndicatorColumns(osheet, Replace(varArgs(5), "-", " "))
                For i = LBound(cols) To UBound(cols)
                    Col = CInt(cols(i))
                    .Cells(1, j).value = sheets(osheet).Cells(1, Col).value
                    .Cells(2, j).FormulaR1C1 = "=IF(OFFSET('" & osheet & "'!RC[" & (-j + Col) & _
                                               "],R1C" & k & ",0)<>"""",OFFSET('" & osheet & _
                                               "'!RC[" & (-j + Col) & "],R1C" & k & ",0),"""")"
                    rstr = rstr & "," _
                            & Cells(2, j).Address + ":" + Cells(endRow, j).Address
                    j = j + 1
                Next i
                col_add = UBound(cols) - LBound(cols) + 1
            End If
          
            .Cells(2, 2).Resize(1, 5 + col_add).AutoFill _
                                Destination:=.Cells(2, 2).Resize(endRow, 5 + col_add), _
                                Type:=xlFillDefault
        End With
        fillChartRngMR = Split(rstr, ",")
        Exit Function
      
    EH_fillChartRngMR:
        MsgBox "Error(" & Err.Number & ") " & Err.Description & " (on fillChartRngMR)"
    End Function
   
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ☞ 기존의 'fillChartRng'와 동일한 기능을 하지만, 캔들차트 데이터 범위와 지표 데이터 범위를 함께 구성합니다.
  ☞ 실제 지표 데이터의 위치를 찾기 위해 'getIndicatorColumns' 함수를 호출합니다. 아래 소스를 참고하여 입력하십시요.

  ※ (함수 'getIndicatorColumns' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Function getIndicatorColumns(sn, istr) As Variant
        Dim sc As String, indi() As CStockIndicator, i_tp As indicator_t
        Dim tstr As String, cols As String
        On Error GoTo EH_getIndicatorColumn:
        For i = 1 To UBound(ss_list)
            If ss_list(i, 2) = sn Then sc = ss_list(i, 1)
        Next i
        i_tp = indicator_type(istr)
        If i_tp.i_type = 0 Then GoTo MyExit:
        tstr = i_tp.i_type & " " & i_tp.m_type
        For i = LBound(i_tp.peri) To UBound(i_tp.peri)
            tstr = tstr & " " & i_tp.peri(i)
        Next i
       
        indi = feeders.Item(sc).TIndicator
        For i = LBound(indi) To UBound(indi)
            Dim tin As CStockIndicator
            Set tin = indi(i)
            If tstr = tin.printMe() Then
                cols = tin.Col
                If (i_tp.i_type And idx_sig) = idx_sig Then _
                    cols = cols & " " & tin.Col + 1
                GoTo MyExit:
            End If
        Next i
       
    MyExit:
        getIndicatorColumns = Split(cols, " ")
        Exit Function
    EH_getIndicatorColumn:
        Debug.Print "Error(" & Err.Number & ") " & Err.Description & " [getIndicatorColumns]"
        GoTo MyExit:
    End Function
   
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ☞ 위 소스와 같이 전역변수 'ss_list'로부터 종목코드를 조회하고, 해당 종목코드의 'CCandleFeeder'로부터 'pTIndicator' 배열을 얻은 후, 일치하는 지표의 column 정보를 획득합니다.
  ☞ 위 함수는 전역변수 'ss_list'가 선언된 모듈에 작성해야 합니다.

  다음은 'drawChartTWHmR'을 신규로 작성할 차례입니다.
  
  ※ (함수 'drawChartTWHmR' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Function drawChartTWHmR(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)(LBound(varArgs(0)))
        cht.Chart.ChartType = varArgs(1)
      
        If UBound(varArgs) > 6 Then
            cht.Name = varArgs(7)
        End If
          
        For i = LBound(varArgs(0)) + 1 To UBound(varArgs(0))
            With cht.Chart.SeriesCollection.NewSeries
                .values = varArgs(0)(i)
                On Error Resume Next ''' For Excel2003 compatibility '''''''''''''''''''
                .AxisGroup = xlSecondary
                On Error GoTo 0      ''' For Excel2003 compatibility '''''''''''''''''''
                .Type = xlLine
                .MarkerStyle = xlNone
                If i > LBound(varArgs(0)) + 1 Then
                    .Border.LineStyle = xlDot
                    .Name = "Signal"
                Else
                    .Format.Line.Weight = 2#
                    .Name = "Indicator"
                End If
            End With
        Next i
      
        Call rescaleChart(cht)
        For k = 1 To 4
            cht.Chart.Legend.LegendEntries(1).Delete
        Next k
      
        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
                .OnAction = "onChartRefresh"
            End With
            End If
        End If
      
        Set CHTsheet = Nothing
        Set cht = Nothing
    End Function
   
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  ☞ 캔들 차트는 차트의 'xlPrimary' 영역에, 지표와 Signal은 'xlSecondary' 영역에 출력하도록 했습니다. 

  ☞ 차트의 캔들 영역과 지표 영역의 Scale 처리를 위해 'rescaleChart' 함수를 호출합니다. 아래 소스를 참고하십시요.
  ☞ 마찬가지로 차트가 스크롤될 때 Scale 처리를 위해 'onChartRefresh'를 호출합니다.
  
  ※ (함수 'rescaleChart' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Function rescaleChart(ch)
        On Error GoTo EH_rescaleChart:
        scH = 2
        scL = 3
       
        s_max = WorksheetFunction.Round(WorksheetFunction.Max _
                                       (ch.Chart.SeriesCollection(scH).values), 0)
        s_min = WorksheetFunction.Round(WorksheetFunction.Min _
                                       (ch.Chart.SeriesCollection(scL).values), 0)
        pDiff = WorksheetFunction.Round((s_max - s_min) / 10, 0)
       
        s_max = s_max + pDiff
        s_min = s_min - pDiff
       
        If ch.Chart.SeriesCollection.Count > 4 Then
            scOSC = 5
            s_min = s_min - 4 * pDiff
           
            If InStr(ch.Name, "RSI") > 1 Then
                With ch.Chart.Axes(xlValue, xlSecondary)
                    .MaximumScale = 400
                    .MinimumScale = 0
                End With
            Else
                osc_max = WorksheetFunction.Max(ch.Chart.SeriesCollection(scOSC).values)
                osc_min = WorksheetFunction.Min(ch.Chart.SeriesCollection(scOSC).values)
                If ch.Chart.SeriesCollection.Count > 4 Then
                    sig_max = WorksheetFunction.Max _
                                            (ch.Chart.SeriesCollection(scOSC+1).values)
                    sig_min = WorksheetFunction.Min _
                                            (ch.Chart.SeriesCollection(scOSC+1).values)
                    osc_max = WorksheetFunction.Max(osc_max, sig_max)
                    osc_min = WorksheetFunction.Min(osc_min, sig_min)
                End If
                oDiff = (osc_max - osc_min) / 10
                With ch.Chart.Axes(xlValue, xlSecondary)
                    .MaximumScale = osc_min + 48 * oDiff
                    .MinimumScale = osc_min - oDiff
                    .TickLabelPosition = xlNone
                End With
            End If
        End If
       
        With ch.Chart.Axes(xlValue, xlPrimary)
            .MaximumScale = s_max
            .MinimumScale = s_min
        End With
       
        Exit Function
    EH_rescaleChart:
        Debug.Print "Error(" & Err.Number & ") " & Err.Description & " [
rescaleChart]"
    End Function

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
   

  ※ (스클롤바 이벤트 매크로 'onChartRefresh' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Sub onChartRefresh()
        Debug.Print "[onChartRefresh]1 " & ", " & Timer
        Dim ch As ChartObject, x As Variant
       
        On Error GoTo EH_onChartRefresh:
        Set ch = ActiveSheet.ChartObjects(1)
        Call rescaleChart(ch)
       
        Set ch = Nothing
        Debug.Print "[onChartRefresh]2 " & ", " & Timer
        Exit Sub

    EH_onChartRefresh:
        Debug.Print "Error(" & Err.Number & ") " & Err.Description & " [onChartRefresh]"
    End Sub
   
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''   
  
  'onChartChange' 소스를 신규로 작성한 'fillCahrtRngMR'와 'drawChartTWHmR' 함수를 사용하도록 수정하겠습니다. 
  
  ※ (수정 후 'onChartChange' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Sub onChartChange(ParamArray varArgs() As Variant)
        If UBound(varArgs) < 3 Then _
            Err.Raise 8902, "onChartChange", "ParamArray too short"
           
        Dim rngs() As Range, chtRng As Variant, sn As String
        Dim ch As ChartObject, sbar As ScrollBar, istr As String, cn As String
        On Error GoTo EH_onChartChange:
        Application.ScreenUpdating = False
       
        sn = Cells(2, 2).value
        If UBound(varArgs) > 3 Then sn = varArgs(4)
        If sn = xlNullString Then Exit Sub
       
        csheet = ActiveSheet.Name
        If Not sheets(csheet).ChartObjects.Count = 0 Then
            Set ch = sheets(csheet).ChartObjects(1)
            istr = ch.Name
            istr = Right(istr, Len(istr) - InStr(InStr(istr, "-") + 1, istr, "-"))
            ch.Delete
            Set sbar = Sheets(csheet).ScrollBars(1)
            sbar.Delete
        End If
       
        If UBound(varArgs) > 4 Then
            chtRng = fillChartRngMR(csheet & "_AUX", sn, True, 2, varArgs(3), varArgs(5))
            cn = sn & "-" & varArgs(5)
        Else
            chtRng = fillChartRngMR(csheet & "_AUX", sn, True, 2, varArgs(3), istr)
            cn = sn & "-" & istr
        End If
        sheets(csheet).Select
        ReDim rngs(LBound(chtRng) To UBound(chtRng))
        For i = LBound(chtRng) To UBound(chtRng)
            Set rngs(i) = sheets(csheet & "_AUX").Range(chtRng(i))
        Next i
       
        endRow = sheets(sn).Cells(1, 1).End(xlDown).Row
       
        drawChartTWHmR rngs, _
                     varArgs(0), varArgs(1), varArgs(2), csheet, _
                     csheet & "_AUX" & "!" & Cells(1, 1).Address, _
                     WorksheetFunction.Max(endRow - varArgs(3) - 1, 0), _
                     cn
       
        Set ch = Nothing
        Set sbar = Nothing
        Application.ScreenUpdating = True
        Exit Sub

    EH_onChartChange:
        MsgBox "Error(" & Err.Number & ") " & Err.Description & " [onChartChange]"
    End Sub

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ☞ 차트 이름에 지표정보를 추가했습니다. 'CCandleFeeder'의 'refreshChart' 함수를 수정해야 합니다.

  ※ (수정 후 'CCandleFeeder'의 'refreshChart' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Private Function refreshChart(csheet)
        Dim ch As ChartObject, sbar As ScrollBar
        Debug.Print "Chart Refreshing..."
        If Not sheetExist(csheet) Then GoTo SkipChartRefreshing:
        On Error Resume Next
        Set ch = Sheets(csheet).ChartObjects(1)
        If Err.Number <> 0 Then
            Err.Clear
            GoTo SkipChartRefreshing:
        End If
        If Not InStr(ch.Name, pCandleSheet) > 0 Then GoTo SkipChartRefreshing:
        Set sbar = Sheets(csheet).ScrollBars(1)
        sbar.Max = sbar.Max + 1
        sbar.Value = sbar.Value + 1
        Set ch = Nothing
        Set sbar = Nothing
    SkipChartRefreshing:
        Debug.Print "End Chart Refreshing"
    End Function

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  ☞ 하이라이트된 부분이 수정된 곳입니다.
      
  지금까지 한 작업의 결과를 테스트해 보겠습니다. 먼저 'Schedule' 버튼을 클릭하여 'FSchedule' 폼이 팝업되면 종목과 지표를 선택한 후 'OK' 버튼을 클릭합니다. ☞ 이 과정을 선행하지 않으면 'CCandleFeeder' 오브젝트 목록(Collection)이 생성되지 않은 상태이므로 차트에 지표가 출력되지 않습니다.
  
 다음 그림과 같이  'Chart' 버튼을 클릭하여 폼이 팝업되면 지표를 선택하고 'OK' 버튼을 누릅니다.
  
그림 4-1. 테스트 결과 1
  
  그림 4-2와 같이 지표가 포함된 캔들차트를 확인할 수 있습니다.
   
그림 4-2. 테스트 결과 2
 
  일단 의도한대로 완성은 되었지만, 불편한 점이 있습니다. 차트 출력 시트('CHARTS')에서 지표를 전환하는 것이 불가능하군요.
  
  작업을 조금 더 해서 차트에 포함된 지표의 전환이 쉬워지도록 개선해 보겠습니다. 'CHARTS' 시트의 생성은 'createChartSheet' 함수가 담당하므로, 'createChartSheet'을 수정하면 될 것입니다.
  
  'CHARTS' 시트에 버튼을 하나 추가하고, 클릭할 경우 폼 'FChart'가 팝업되도록 하겠습니다. 
  
  ※ (수정 후 '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
          
            Set btn = tmpWS.Buttons.Add(tmpWS.Cells(2, 5).Width * 4, _
                                        tmpWS.Cells(2, 5).Height, _
                                        tmpWS.Cells(2, 5).Width, _
                                        tmpWS.Cells(2, 5).Height)
            btn.Name = "ButtonIndicator"
            btn.OnAction = "'Chart_Click True'"
            btn.Characters.Text = "Indicator"
            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
            GoTo SkipControl:
        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

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  ☞ 추가한 버튼의 이벤트 매크로를 'Chart_Click'으로 지정했는데, boolean형 인자로 true를 넘겨줍니다. 'FChart'의 차트 크기를 지정하는 페이지를 비활성화하기 위한 것입니다.
  ☞ 'Chart_Click' 매크로를 아래 소스로 수정하십시요.

  ※ (수정 후 'Chart_Click' 소스)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Sub Chart_Click(ParamArray varArgs() As Variant)
        Dim frm As FChart
      
        Set frm = New FChart
        Load frm
        If Not UBound(varArgs) < 0 Then frm.MultiPage1.Pages(0).Enabled = False
        frm.Show
    End Sub

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

  ☞ 하이라이트된 부분이 수정된 곳입니다.
  
  모든 작업이 끝났습니다. 'CHARTS' 시트를 삭제한 후 다시 테스트해 보겠습니다.
     
그림 4-3. 테스트 결과 3
     
  위 그림 4-3과 같이 버튼이 추가된 'CHARTS' 시트를 확인할 수 있습니다.
  
  ※ 완성된 엑셀 파일입니다.  
  
  ※ 다음 글에서 지표의 활용과 관련된 내용을 다루겠습니다.

     

댓글 없음:

댓글 쓰기