=====
cybosplus(증권API) + golang 을 이용하여 투자 분석 및 시스템트레이딩을 할수 있도록 만든 간단한 Wrapper 라이브러리 입니다.
go get github.com/ippoeyeslhw/cpgo
cpgo 는 사이보스플러스를 go언어에서 사용할수 있도록 Wrapping 한 것입니다. go-ole패키지를 기반으로 작성되었습니다.
go-ole패키지를 기반으로 작성되었으므로 많은 부분을 의존합니다. 반드시 임포트하여야합니다.
import (
ole "github.com/go-ole/go-ole"
"github.com/ippoeyeslhw/cpgo"
)
반드시 cointialize, uncoinitialize 호출 해야합니다.
ole.CoInitialize(0)
defer ole.CoUninitialize()
도움말을 열어봤을때 좌측에 보이는 주요 네가지 라이브러리인
- CpDib : 혹은 DSCBO1
- CpSysDib
- CpTrade
- CpUtil
각각에 속하는 coclass들을 "."으로 연결하여 객체를 생성할수 있습니다. Create 메서드로 COM객체 생성을 Release 메서드로 헤제를 할수 있습니다.
stkmst := &cpgo.CpClass{}
stkmst.Create("CpDib.StockMst")
defer stkmst.Release()
기본적인 동작을 위해 구현된 주요 인터페이스 메서드들입니다.
- SetInputValue : 입력데이터 세팅
- Request : Non-blocking 요청
- BlockRequest : Blocking 요청
- Subscribe : 실시간 수신 요청
- SubscribeLastest : 실시간 수신 (스냅샷성 데이터) 요청
- Unsubscribe : 실시간 수신 해지
- GetHeaderValue : 수신 헤더데이터
- GetDataValue : 수신 데이터
stkmst.SetInputValue(0, "A000270")
stkmst.BlockRequest()
fmt.Println(stkmst.GetHeaderValue(1).Value())
Property값을 가져오려면 getter를 사용하여야 합니다. 도움말의 Property명에 앞에 Get을 붙인 메서드명을 사용합니다.
tmp := &CpClass{}
tmp.Create("CpUtil.CpCybos")
defer tmp.Release()
fmt.Println(tmp)
fmt.Println("isconnect: ", tmp.GetIsConnect().Value())
fmt.Println("servertype: ", tmp.GetServerType().Value())
fmt.Println("remain time: ", tmp.GetLimitRequestRemainTime().Value())
Received 이벤트는 Receiver 인터페이스를 구현하면 됩니다.
type Receiver interface {
Received(*CpClass)
}
이를 BindEvent 메서드를 사용하여 이벤트를 지정할수 있습니다. 이벤트를 해지하려면 UnbindEvent 메서드를 사용합니다.
type RqTestStruct struct {
}
func (t *RqTestStruct) Received(c *CpClass) {
fmt.Println(c.GetHeaderValue(1).Value())
c.UnbindEvent() // 이벤트 바로 헤제
}
//... 생략
evnt := &RqTestStruct{}
cpobj.BindEvent(evnt)
PeekMessage 를 기반으로 동작하는 PumpWaitingMessages 함수를 제공합니다. 이를 사용하여 Received이벤트를 수신할때까지 대기할수 있습니다.
for {
PumpWaitingMessages()
time.Sleep(1)
}
통신방식을 기준으로 작성한 몇가지 예제입니다.
package main
import (
"fmt"
ole "github.com/go-ole/go-ole"
"github.com/ippoeyeslhw/cpgo"
)
func main() {
ole.CoInitialize(0)
defer ole.CoUninitialize()
stkmst := &cpgo.CpClass{}
stkmst.Create("CpDib.StockMst")
defer stkmst.Release()
stkmst.SetInputValue(0, "A000270")
stkmst.BlockRequest()
fmt.Println(stkmst.GetHeaderValue(1).Value())
}
// 결과:
// 기아차
package main
import (
"fmt"
"time"
ole "github.com/go-ole/go-ole"
"github.com/ippoeyeslhw/cpgo"
)
type ContEvnt struct {
isDone bool
}
func (s *ContEvnt) Received(c *cpgo.CpClass) {
count := c.GetHeaderValue(2).Value().(int16) // 수신개수
fmt.Println("response count: ", count)
for i := 0; i < int(count); i++ {
fmt.Println(
c.GetDataValue(1, i).Value(), // 종목코드
c.GetDataValue(4, i).Value()) // 내용
}
if c.GetContinue().Value() == int32(1) { // 연속데이터 있음
fmt.Println("next request")
c.Request() // 재요청
} else {
s.isDone = true
}
}
func main() {
ole.CoInitialize(0)
defer ole.CoUninitialize()
cpmw := &cpgo.CpClass{}
cpmw.Create("CpSysDib.CpMarketWatch")
defer cpmw.Release()
evnt := &ContEvnt{false}
cpmw.BindEvent(evnt)
// 연속조회 request 갯수제한 유의
cpmw.SetInputValue(0, "*") // 전종목
cpmw.SetInputValue(1, "2") // 공시정보
cpmw.Request()
for evnt.isDone == false {
cpgo.PumpWaitingMessages()
time.Sleep(1)
}
cpmw.UnbindEvent()
}
// 결과:
//response count: 20
//A151910 나노스(주) 주권매매거래정지기간변경(개선기간 부여)
//A043290 케이맥(주) 단일판매ㆍ공급계약체결
//A151910 나노스(주) 기타시장안내(기업심사위원회 심의결과 및 개선기간 부여 안내)
//A900050 중국원양자원유한공사 기타 경영사항(자율공시)(자회사의 주요경영사항)
//A043710 (주)서울리거 기타시장안내(상장적격성 실질심사사유 추가 관련 )
//A065420 (주)에스아이리소스 주권매매거래정지(불성실공시법인 지정)
//A065420 (주)에스아이리소스 불성실공시법인지정(공시불이행)
//A011200 현대상선(주) 전환가액ㆍ신주인수권행사가액ㆍ교환가액의 조정(안내공시)
//A011200 현대상선(주) 전환가액ㆍ신주인수권행사가액ㆍ교환가액의 조정(안내공시)
// ...
// ...
package main
import (
"fmt"
"time"
ole "github.com/go-ole/go-ole"
"github.com/ippoeyeslhw/cpgo"
)
type SubTestStruct struct {
cont bool
cnt int
}
func (s *SubTestStruct) Received(c *cpgo.CpClass) {
fmt.Printf("(%f)%d , (%f)%d\n",
c.GetHeaderValue(14).Value(), // 1차 매수호가
c.GetHeaderValue(15).Value(), // 1차 매수잔량
c.GetHeaderValue(25).Value(), // 1차 매도호가
c.GetHeaderValue(26).Value()) // 1차 매도잔량
if s.cnt > 100 {
// 100건이 넘을시 중단
s.cont = false
}
s.cnt++
}
func main() {
ole.CoInitialize(0)
defer ole.CoUninitialize()
tmp := &cpgo.CpClass{}
tmp.Create("CpSysDib.CmeCurr")
defer tmp.Release()
fmt.Println(tmp)
evnt := &SubTestStruct{true, 0}
tmp.BindEvent(evnt)
fmt.Println(tmp)
// 야간 CME 선물시장 밤에 테스트할것
tmp.SetInputValue(0, "101L9")
tmp.Subscribe()
fmt.Println("sub/pub start")
for evnt.cont == true {
cpgo.PumpWaitingMessages()
time.Sleep(1)
}
tmp.Unsubscribe()
tmp.UnbindEvent()
}
// 결과:
//sub/pub start
//(251.449997)30 , (251.500000)2
//(251.449997)35 , (251.500000)2
//(251.449997)35 , (251.550003)34
//(251.449997)35 , (251.550003)34
//(251.449997)35 , (251.550003)34
//(251.449997)35 , (251.500000)1
// ...
// ...
Subscribe 통신방식으로 이벤트 수신을 지속적으로 받다보면 이벤트 수신이 중간에 끊어졌다가 다시 연결되었다가 하는 현상이 발견됩니다. COM 객체는 이벤트 싱크를 맺은 윈도우 스레드에만 메세지를 전달합니다. Go언어의 스케줄러는 다수의 OS Thread 와 다수의 Goroutine 을 가지고 스케줄링 하여 윈도우 스레드가 중간에 바뀌어 끊어지는 것입니다. 이를 방지하기 위해 윈도우 스레드 함수 Wrapper를 사용할수 있습니다.
type Background func(uintptr) uintptr
uintptr 을 인자로 받고 uintptr을 리턴하는 함수를 선언합니다.
func CreateThread(fnc Background, arg1 uintptr) (ret int32, err error)
함수와 인자로 넘겨줄 값을 넘겨주어 스레드를 생성합니다.
type CmeCurEvnt struct {
}
func (ce *CmeCurEvnt) Received(c *cpgo.CpClass) {
fmt.Printf("[%v] (%f)%d , (%f)%d\n",
time.Now(),
c.GetHeaderValue(14).Value(), // 1차 매수호가
c.GetHeaderValue(15).Value(), // 1차 매수잔량
c.GetHeaderValue(25).Value(), // 1차 매도호가
c.GetHeaderValue(26).Value()) // 1차 매도잔량
}
func loop(p uintptr) uintptr {
// 윈도우 스레드에서 돌아갈 코드
ole.CoInitialize(0)
defer ole.CoUninitialize()
// create cp class
tmp := &cpgo.CpClass{}
tmp.Create("CpSysDib.CmeCurr")
defer tmp.Release()
// event binding
evnt := &CmeCurEvnt{}
tmp.BindEvent(evnt)
defer tmp.UnbindEvent()
// set input values
tmp.SetInputValue(0, "101M3")
tmp.Subscribe()
defer tmp.Unsubscribe()
for {
cpgo.PumpWaitingMessages()
time.Sleep(1)
}
return 0
}
func main() {
// 스레드 생성
cpgo.CreateThread(loop, 0)
// 메인스레드 Waiting
for {
time.Sleep(time.Millisecond * 10)
}
}