elbakramer / koapy

KOAPY 는 키움 OpenAPI 를 Python 에서 쉽게 사용할 수 있도록 만든 라이브러리 패키지 및 툴입니다.
https://koapy.readthedocs.io
MIT License
199 stars 78 forks source link

koapy backtrader sample 사용중 알수 없는 중단 현상 문의 #14

Open solverkim opened 3 years ago

solverkim commented 3 years ago

안녕하세요? koapy 내에 backtrader sample프로그램을 활용하고자 합니다. 몇번의 테스트를 해 보았지만 원인을 찾기가 어렵네요.

kiwoom_broker_iteration을 활용해서 테스트를 해보았습니다. 로직상은 이상없는 것처럼 잘 돌아 갑니다. 그런데 약 2시간 정도(정확하지는 않음, 즉 하루종일 구동할수 없음)를 구동하고나면 아무런 응답이 없습니다. 그냥 realtimedata를 가져오지 못하는 듯합니다.

추정해보다면 어떤 특정 시간을 제한하고 있는 것 처럼 느껴지는데.. 이건 느낌일 뿐이고..

그래서 realtimedata를 for loop로 구동했더니 이건 하루종일 구동되었습니다. backtrader에서 고동하는 하기코드는 중단되는데.. backtrader의 qcheck일까 싶어서 조정해 보았지면 Check시간만 길어지고 약 2 ~3시간후에는 realdata 수신이 되지 않습니다.

혹 방법이 있을까요? QueueBasedBufferedIterator가 import 되지 않은 프로그램이 있어서 import 해 놓으시면 감사하겠습니다.

좋은 프로그램 공개해 주셔서 많은 도움이 되겠습니다. 고맙습니다.

data = kiwoomstore.getdata(dataname='005930', backfill_start=False, timeframe=bt.TimeFrame.Ticks, compression=1, qcheck=10)
data.resample(timeframe=bt.TimeFrame.Minutes, compression=1)
data.addfilter(bt.filters.SessionFiller)
cerebro.adddata(data, name='005930')
cerebro.replaydata(data, name='005930-1day', timeframe=bt.TimeFrame.Days, compression=1)

data = kiwoomstore.getdata(dataname='035420', backfill_start=False, timeframe=bt.TimeFrame.Ticks, compression=1, qcheck=10)
data.resample(timeframe=bt.TimeFrame.Minutes, compression=1)
data.addfilter(bt.filters.SessionFiller)
cerebro.adddata(data, name='035420')
cerebro.replaydata(data, name='035420-1day', timeframe=bt.TimeFrame.Days, compression=1)
elbakramer commented 3 years ago

안녕하세요. 리포트 감사합니다.

말씀주신 부분의 기능은 초기 개발과정에서도 충분한 시간을 들여서 테스트를 해보지 못한 부분인 것 같습니다. 그리고 사실 당분간에도 개장시간동안 그렇게 길게 테스트를 하면서 확인해볼만한 여유가 없을 것 같아서요. 당장 관련해서 원인을 파악해서 패치를 적용한다던가 하는건 어려울 것 같다는점 먼저 말씀드립니다.

대신 현재 제가 추측하는 원인 두가지 정도 적어봅니다.

1. data 의 구현에는 문제가 없지만 이외에 나머지 backtrader 관련 기능이 어떻게 동작하는지 충분히 알지 못하고 사용

추측하기로 예시 스크립트에서 resample() addfilter() replaydata() 같이 추가적으로 설정한 부분들이 뭔가 의도한대로 동작하지 않아서 그런게 아닌지 하는 생각이 드는데요. 혹시 그런 부분들을 다 제거하고 data 자체로만 테스트를 한번 해보면 어떨까 싶습니다.

data = kiwoomstore.getdata(dataname='005930', backfill_start=False, timeframe=bt.TimeFrame.Ticks, compression=1, qcheck=10)
cerebro.adddata(data, name='005930')

2. data 의 구현 자체에 문제가 있음

위의 테스트에서 이전과 동일한 문제가 발생한다면 아마도 koapy 내부의 data 구현에 문제가 있는것으로 범위를 좁힐 수 있어 보입니다. 다만 여기서 더 구체적인 원인은 아직 잘 모르겠네요. 대충 queue 와 관련된 부분 어딘가에서 락이 걸려버린건 아닌가 정도로만 막연하게 추측하고 있습니다.

일단 KiwoomOpenApiPlusData 클래스의 구현에서부터 실시간 데이터 연동과 관련된 KiwoomOpenApiPlusEventStreamer 클래스의 구현까지의 정도의 범위내에서 살펴보면 어떨까 싶습니다.

참고로 KiwoomOpenApiPlusData 를 포함한 backtrader 관련 코드들은 backtrader 에서 oanda 브로커에 해당하는 기존 구현들을 참고해서 작성된거라 그쪽 부분의 참고가 필요할 수도 있어 보이네요.

elbakramer commented 3 years ago

QueueBasedBufferedIterator가 import 되지 않은 프로그램이 있어서 import 해 놓으시면 감사하겠습니다.

말씀주신 부분은 https://github.com/elbakramer/koapy/commit/ab41cbc7f9cc8be848475db258cb24ef4fb739b8 을 통해 v0.3.5 으로 배포해두었습니다. 다시 한번 리포트 감사합니다.

elbakramer commented 3 years ago

해당 내용 #36, #40, #41 등과 관련이 있었지 않나 싶어서 우선 링크해놓습니다.

dh377 commented 2 years ago

에러 메시지 없이 멈추는 경우를 저도 경험했습니다.

에러가 특별히 발생하지 않고 멈추는 경우는 대개 쓰레드 관련 문제인 경우가 많은데, Kiwoom에서 답변한 내용을 검토해본 결과 일단 Kiwoom OpenAPI+는 Thread-Safe가 아니라고 합니다. 즉, 만약 어딘가에서 API 함수/이벤트를 처리할 때, 동시에 다른 함수를 처리하려고 하면, (나의 콜은 다른 콜이 끝나기를 기다리는데 다른 콜은 내가 끝나기를 기다린다. 즉 circular waiting) 두 함수 모두가 영원히 기다리는 Deadlock이 발생할 수 있다는 말이며, 이런 종류의 데드락은 함수를 콜하는 횟수와 빈도가 많아질수록 멈출 확률이 더 높아집니다.

코드를 살펴보면 Kiwoom OpenAPI를 호출하는 부분 self.control.*들이 보이는데, 이 컨트롤들이 동시에 동기식으로 호출되서는 안 됩니다. 예를 들어, 이벤트 (OnReceiveTRData(), OnReceiveRealData())가 발생하면 이벤트가 발생한 중간에 꼭 불러야 하는경우를 제외하면 (GetCommData(), GetCommRealData()) 되도록 비동기식으로 호출해서, 다른 함수가 IO 중일 때 그 함수에게 쓰레드 락을 건네주고 기다릴 수 있도록 해야되겠지요... 이미 API 컨트롤 함수들은 @elbakramer 님께서 async_call()로 비동기식으로 부를 수 있게 해놓으셨습니다.

참여자분들 중에 KOAPY가 응답이 없는 경우가 발생하면, 제가 쓴 #78 참고하셔서 API 컨트롤을 비동기 호출하도록 수정해보셨으면 좋겠습니다. (저는 그냥 되는대로 썼는데, async/await 사용해서 더 깔끔하게 코드를 쓸 수 있는 방법이 있을라나 모르겠습니다...)

호출 실패하는 경우가 상관 없으면 그냥 동기 호출을 async_call()을 붙여 비동기 호출로만 바꿔도 괜찮을 것 같습니다.

dh377 commented 2 years ago

async call은 일단 현재의 쓰레드는 정의된 콜을 바로 실행하지 않고, 별도의 쓰레드를 생성시켜서 그 쓰레드에게 미래의 언젠가 처리를 완료시킵니다. 처리 완료 후 결과에 따라 오류처리가 필요하면, 오류 처리를 위한 callback을 미리 정의해서 async로 부른 펑션을 처리하게 될 그 다른 쓰레드에게 미리 넘겨줘야 합니다.

https://github.com/elbakramer/koapy/blob/b4cc36bc997daf90f117c3d908c8b7b9fd3a9b22/koapy/backend/kiwoom_open_api_plus/grpc/event/KiwoomOpenApiPlusRealEventHandler.py#L181-L215

elbakramer commented 2 years ago

여기서 언급된 async_call() 함수의 경우, 기존 파이썬의 asyncio 등과 혼동할 가능성도 있고, 실제 Qt 동작과 관련시켜 (QueuedConnection) 좀 더 명확하게 하기 위해서 버전 v0.9.0 에서 queuedCall() 로 이름을 바꾸었으니 참고 바랍니다.

https://github.com/elbakramer/koapy/blob/ff982dbfc15beab6aec98973c205fe8851e755a5/koapy/backend/kiwoom_open_api_plus/utils/pyside2/QSlotLikeExecutor.py#L62-L63

https://github.com/elbakramer/koapy/blob/ff982dbfc15beab6aec98973c205fe8851e755a5/koapy/backend/kiwoom_open_api_plus/utils/pyside2/QRateLimitedExecutor.py#L98-L99