본문 바로가기
Algorithm Trading/ComDon 프로그램 개발이야기

5. 주문딜레이 시간 체크 / 비동기 처리의 장점

by 컴돈AI 2024. 1. 9.

목차

    주문딜레이 시간 체크

    • TradingView를 이용하여 자동매매를 구현하게 되면 다음과 같은 방법으로 주문 체결이 일어나게 됩니다.
      • TradingView -> Alert 발생 -> Comdon 프로그램 -> 바이낸스 API 호출 -> 바이낸스 거래 체결
    • 따라서 딜레이가 발생할 수 있습니다. 딜레이가 어느 정도 발생하는지를 체크를 해보도록 하겠습니다.
    • 또한 비동기의 장점을 살펴보기 위해서 동기 방식과 비동기 방식의 시간을 비교하도록 하겠습니다.
    • 파인스크립트 코드
      • 한번에 많은 주문을 발생시키기 위해 pyramiding을 1000으로 설정하고, calc_on_every_tick = true로 설정하였습니다.
      • //@version=5
        //@strategy_alert_message {{strategy.order.alert_message}}
        strategy("돌파 체크", overlay = true, calc_on_every_tick = true, pyramiding = 1000)
        
        int length = input.int(15, "Length")
        
        float highest = ta.highest(high, length)[1]
        float lowest  = ta.lowest(low, length)[1]
        
        if close[0] >= highest
            strategy.entry("Buy", strategy.long,alert_message = "[hieghest]  현재가 : "+ str.tostring(close[0]))
        if close[0] <= lowest
            strategy.entry("Sell", strategy.short,alert_message = "[lowest]  현재가 : "+ str.tostring(close[0]))
        
        var realTimeStart = timenow
        
        bgcolor(time_close >= realTimeStart ? color.new(color.orange, 80) : na)
        
        plot(highest, "Highest", color = color.lime)
        plot(lowest, "Lowest", color = color.red)
      • entry 신호가 발생하면 alert_message에 있는 내용에 Comdon 프로그램으로 전송되게 됩니다.

    동기 방식

    • 동기 코드
      • from fastapi import FastAPI, Request, HTTPException
        from contextlib import asynccontextmanager
        from datetime import datetime
        import uvicorn
        import ccxt
        
        
        @asynccontextmanager
        async def lifespan(app: FastAPI):
            print("서버접속")
            yield
            print("서버종료")
        
        
        app = FastAPI(lifespan=lifespan)
        
        
        ALLOWED_IPS = {"52.89.214.238", "34.212.75.30", "54.218.53.128", "52.32.178.7"}
        
        
        @app.middleware("http")
        async def ip_filter_middleware(request: Request, call_next):
            client_ip = request.client.host
            if client_ip not in ALLOWED_IPS:
                raise HTTPException(status_code=403, detail="IP Address not allowed")
            response = await call_next(request)
            return response
        
        
        @app.post("/webhook")
        async def webhook(request: Request):
            alert_message = await request.body()
            alert_message_str = alert_message.decode("utf-8")
            alert_time = datetime.now()
            print(alert_message_str)
        
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            # buy, sell
            order = exchange.create_order("ETH/USDT", "MARKET", "buy", 0.009)
            print(f"alert_time : {alert_time}, complete time : {datetime.now()} ")
        
            return {"message": "Received"}
        
        
        if __name__ == "__main__":
            uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
        • async와 await을 사용했지만, 실질적으로 주문을 하는 코드인 exchange.create_order 부분이 동기 코드이기 때문에 주문이 체결되는 시간 동안 여기서 전체 프로그램이 대기하게 됩니다. 즉, 동기적으로 코드가 작동하게 됩니다.
    • 결과
      • 얼러트 로그
      • 출력 결과
      • 시간을 보게되면 4시 31분 31초에 한 번에 많은 주문(alert_message)이 들어온것을 볼 수 있습니다. 하지만 이제 동기적으로 코드를 짠 경우 하나의 alert 에 대해서 체결이 완료되어야지만 다음 alert 가 체결되기때문에 31초에 발생한 alert가 주문이 밀려 35초에 체결된것을 확인할 수 있습니다.
        • 즉, 동기적으로 코드를 작성한 경우 delay가 심하게 발생할 수 있습니다. (초단타는 어려울 수 도 있습니다.)

    비동기 방식

    • 비동기 코드
      • from fastapi import FastAPI, Request, HTTPException
        from contextlib import asynccontextmanager
        from datetime import datetime
        import uvicorn
        import ccxt.async_support as ccxt
        
        
        @asynccontextmanager
        async def lifespan(app: FastAPI):
            print("서버접속")
            yield
            print("서버종료")
        
        
        app = FastAPI(lifespan=lifespan)
        
        
        ALLOWED_IPS = {"52.89.214.238", "34.212.75.30", "54.218.53.128", "52.32.178.7"}
        
        
        @app.middleware("http")
        async def ip_filter_middleware(request: Request, call_next):
            client_ip = request.client.host
            if client_ip not in ALLOWED_IPS:
                raise HTTPException(status_code=403, detail="IP Address not allowed")
            response = await call_next(request)
            return response
        
        
        @app.post("/webhook")
        async def webhook(request: Request):
            alert_message = await request.body()
            alert_message_str = alert_message.decode("utf-8")
            alert_time = datetime.now()
            print(alert_message_str)
        
            exchange = ccxt.binanceusdm(
                {
                    "apiKey": "",
                    "secret": "",
                    "enableRateLimit": True,
                }
            )
        
            # buy, sell
            order = await exchange.create_order("ETH/USDT", "MARKET", "buy", 0.009)
            print(f"alert_time : {alert_time}, complete time : {datetime.now()} ")
        
            await exchange.close()
        
            return {"message": "Received"}
        
        
        if __name__ == "__main__":
            uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
         
        • import ccxt.async_support as ccxt를 통해 손쉽게 ccxt라이브러리를 비동기 코드로 작성할 수 있습니다.
        • 또한 주문 체결 코드 앞에 await을 붙여줘야지만 주문체결을 대기하는 상태에서 다른 주문을 추가로 받을 수 있게 됩니다.
    • 결과
      • 얼러트 로그
      • 출력 결과
      • 시간을 살펴보게 되면 한 번에 아주 많은 alert 주문이 들어오더라도 많은 alert 주문이 비동기적으로 체결되는 것을 확인할 수 있습니다. (즉, 여러 주문이 들어와도, 주문 체결을 대기하는 동안 다른 주문이 밀리지 않고 다른 주문들도 접수가 됩니다.)
        • alert가 발생하고 alert가 발생한 주문에 대해 체결되지 않았지만, 또 들어온 alert에 대해서 주문 접수를 넣을 수 있습니다. (cf. 동기 코드에서는 alert가 발생하면 해당 alert가 발생한 주문이 체결될때까지 다른 들어온 alert에 대해서 주문 접수를 넣지 못하고 대기하고 있어야합니다.)

    동기 vs 비동기 결과

    • 위 결과들을 살펴보면 하나의 alert가 발생한 뒤 실제로 체결되는데 약 0.5~0.7초 정도 소요되는 것을 확인할 수 있습니다. 
    • 만약 동기적으로 코드를 구현한다면, 한번에 100개의 주문이 동시에 들어오게 된다면 하나씩 처리해서 아무리 빨라도 모든 주문이 체결되는데 최소 50초 이상 걸릴 것입니다.
      • 하나의 주문을 처리하는데 0.5초라고 가정하면 동기적으로 0.5 * 100  = 50초 정도 소요됩니다.
    • 하지만 비동기적으로 코드를 구현할 경우, 거의 동시에 모든 주문을 접수할 수 있기 때문에 한 번에 100개의 주문이 들어오더라도 모든 주문이 거의 1 초정도안에 해결되게 될 것입니다.
    • 따라서 신호가 발생하자마자 체결하는 것이 중요한 자동매매에서는 비동기적으로 코드를 작성하는 것이 필수적입니다.

     

     

     

     

    'Algorithm Trading > ComDon 프로그램 개발이야기' 카테고리의 다른 글

    7. 이베스트투자증권 OPEN API  (0) 2024.01.22
    6. .env 파일 작성  (0) 2024.01.21
    4. CCXT 라이브러리  (0) 2023.12.30
    3. webhook 연결(포트포워딩)  (1) 2023.12.29
    2. TradingView PineScript  (0) 2023.12.23