BeeBot 자동매매 프로그램 개발_7단계: 비봇 시운전: 코딩으로 격물치지의 뜻일 되새기다.

인공의 엉터리 해석

글을 쉽게 쓰려고 인공한테 물었더니 참 재미있는 답이 나오네요.

나름대로 한자의 뜻을 새겨서 소설을 쓴 것 같습니다. 즉시 싫어요를 눌러주었습니다.

이제 보니 이놈이 모르는게 없는 듯이 지껄이는 허언증 환자네요. 알고 써야겠습니다.

다음 사전에서

이 말의 출전은 대학인데 원문은 다음과 같습니다

물격이후지치(物格而後知致)

직역하면 “사물은 주의깊게 관찰해 보아야 비로소 알게 된다.”란 말입니다.

먹물들은 글자 하나하나를 현학적으로 파고들기 때문에 오히려 본래의 뜻을 왜곡하게 됩니다.

필자와 같은 뻰찌들은 그냥 있는 그대로 직역을 좋아합니다. 그래서 틈틈이 원문을 찾아서 그 본래의 뜻을 확인하곤 합니다.

어찌됐든, 코딩을 한다는 것은 무언가를 잘 작동하게 만드는 작업입니다.

코드를 아무리 멋지게 짰다한들 작동되지 않으면 쓸모 없으니까요.

오늘을 그동안의 작업 결과물만으로 비봇을 시운전해 보려합니다.

작업이 제대로 되었다면 모든 부속품들이 아귀가 맞아들어가고 아무런 오류가 없이 봇이 구동될 것입니다.

그러나 그런 일을 일어나기 힘들죠.

어딘가 구멍이 있기 마련입니다.

코드의 경우라면 인터페이스가 맞지 않거나, 오작동을 일으키는 로직 오류이거나 반드시 발생하게 됩니다.

그래서 시운전을 해 보아야 하고, 시운전을 해보면 작업하는 동안에 주밀하게 관찰하지 못했던 부분을 발견하게 되므로 자연스레 “격물(格物)”하게 되는 것입니다.

우리가 코딩을 통해 이루고자하는 바와 그것을 이루는 방법에 대해 좀더 깊이 이해하게 되는 것입니다.

그러한 관점에서 코딩은 우리의 성현들이 강조해 마지 않았던 격물치지를 체험할 수 있는 간편한 방법 중의 하나라고 필자는 주장하는 바입니다.

각각의 부속품에 대해서는 unit test를 다 했지만 이것들을 모두 조립한 상태에서의 테스트 즉 Integration Test를 해보아야 합니다.

인테그레이션 테스트에서 오류가 발견된 부분을 수정하면 unit test가 깨지게 되고 그 부분을 추가로 수정해주고 나면, 작동이 되는 working version의 비봇을 확보하게 되는 것입니다.

자! 가볼까요?

시운전을 통해서 격물한 내용은 다음과 같습니다.

  1. Position을 조회하면 BTC의 경우는 long과 short 포지션에 대해 데이타가 넘어오는데, ETH의 경우는 empty slice가 넘어와서 봇이 죽는다.
  2. Signal은 모두 4가지 경우의 수가 있어야 하는데 2가지 경우의 수 만으로 구현되었다.
  3. 주문시에 기준가는 포지션 확인때 가져온 MarketPrice를 사용했는데, 그게 있을 때도 있고 없을 때도 있다.

1의 경우는 bitget API가 일관성이 없단 얘긴데…. 흠흠~ 데모트레이딩이라 그런지 확인해 봐야겠습니다.

이미 만들어 놓은 테스트 코드를 이용합니다.

t.Run("get single position", func(t *testing.T) {
		symbol := "ETHUSDT_UMCBL"
		marginCoin := "USDT"
		position, err := account.GetPosition(symbol, marginCoin)
		if err != nil {
			t.Errorf("%v is null", err)
		} else {
			if len(position) != 2 {
				t.Errorf("invalid position %v", position)
			}
		}
	})

Running tool: /usr/local/go/bin/go test -timeout 30s -run ^\QTestAccount\E$/^\Qget_single_position\E$ bitget/beebot -v

=== RUN   TestAccount
=== RUN   TestAccount/get_single_position
{"code":"00000","msg":"success","requestTime":0,"data":[{"marginCoin":"USDT","symbol":"ETHUSDT_UMCBL","holdSide":"long","openDelegateCount":"0","margin":"0","available":"0","locked":"0","total":"0","leverage":20,"achievedProfits":"0","averageOpenPrice":"0","marginMode":"crossed","holdMode":"double_hold","unrealizedPL":"0","liquidationPrice":"0","keepMarginRate":"0.005","marketPrice":"1658.68","cTime":"1675720677135"},{"marginCoin":"USDT","symbol":"ETHUSDT_UMCBL","holdSide":"short","openDelegateCount":"0","margin":"0","available":"0","locked":"0","total":"0","leverage":20,"achievedProfits":"0","averageOpenPrice":"0","marginMode":"crossed","holdMode":"double_hold","unrealizedPL":"0","liquidationPrice":"0","keepMarginRate":"0.005","marketPrice":"1658.68","cTime":"1675720677135"}]}
--- PASS: TestAccount (0.14s)
    --- PASS: TestAccount/get_single_position (0.14s)
PASS
ok  	bitget/beebot	0.507s

테스트 통과하는군요. 데모트레이딩에서는 BTC의 경우만 널포지션 값을 리턴해 주는 모양입니다.

그래도 혹시 모르니까 다른 잡코인도 테스트해 봅니다.

t.Run("get single position", func(t *testing.T) {
		symbol := "MATICUSDT_UMCBL"
		marginCoin := "USDT"
		position, err := account.GetPosition(symbol, marginCoin)
		if err != nil {
			t.Errorf("%v is null", err)
		} else {
			if len(position) != 2 {
				t.Errorf("invalid position %v", position)
			}
		}
	})
}

=== RUN   TestAccount/get_single_position
{"code":"00000","msg":"success","requestTime":0,"data":[]}
    /Users/cheon/work/emily/bitget/beebot/account_test.go:70: invalid position []
--- FAIL: TestAccount (0.25s)
    --- FAIL: TestAccount/get_single_position (0.25s)
FAIL
FAIL	bitget/beebot	0.609s

아니나 다를까 포지션 값이 [] 경우가 있네요.

비트겟 API가 부실한 부분이 있네요.

우리 봇이 이런 예측불가능한 경우에도 정상 작동하도록 예외처리를 해줘야 합니다.

API가 부실하면 클라이언트 코드가 복잡해 질 수 밖에 없죠.

코딩 작업시에 많은 시간과 노력이 투입되는 부분이 지금 하는 디버깅 작업입니다만, 알콜성 치매 환자도 이런 작업이 가능한 이유는 테스트 코드가 있기 때문입니다.

테스트코드를 실행시키면 무엇이 어디서 잘못되었는지 친절하게 알려주기 때문에 그렇습니다.

지금 알바 뛰는 업체의 코드는 수십명이 작업하는데 테스트 코드를 작성하는 사람이 없더이다. 제가 테스트 코드를 푸시해 놓으니까, 담당자가 테스트 코드를 제외하고 머지하더군요.

테스트 코드 없이 코딩하는 분들은 다들 엄청난 천재이거나 오류 발견시에 오류 부분을 찾아내고 수정하느라 고생고생하는 노가다이거나 둘중 하나라고 봅니다.

테스트 코드를 작성하는 것은 작업 당시에는 이중작업이 아닌가 생각하는데, 코드가 늘어나고 복잡도가 증가하게 되면 테스트코드가 있는 경우와 그렇지 않은 경우의 퍼포먼스는 비교할 수 없을 만큼 차이가 납니다.

재수가 없는 경우 하나의 버그로 며칠을 씨름하기도 하고, 버그를 잡아 놓으면 그 버그가 새끼친 버그가 다시 발견되는 악순환을 겪어본 개발자라면 꼭 테스트 코드를 작성하길 권고합니다.

2번의 경우는 선물이기 때문에 롱과 쇼트에 대해서 각각 open, close 시그널을 생성해야하기 때문에 모두 4종의 시그널이 피요합니다.

그런데 롱과 쇼트만 만들어 놓았으니 롱 포지션에 대해서만 시그널이 나오고 쇼트 포지션에 대해서는 시그날이 생성되지 못하게 됩니다.

시그널은 다음과 같이 생겼어야 합니다.

func TestSignal(t *testing.T) {
	signal := Signal{
		Long:  make(map[string]bool),
		Short: make(map[string]bool),
	}
	signal.Long["open"] = true
	signal.Long["close"] = false
	signal.Short["open"] = false
	signal.Short["close"] = true
}

3번의 경우는 복잡한 물건이 출토된 것이라고 봐야 마땅합니다.

우리가 매매주문을 낼때 지정가를 무엇으로 하는가 하는 문제는 매우 다양한 경우의 수가 있습니다.

  1. 시장가
  2. 캔들가
  3. 호가
  4. 기타

시장가는 티커를 확인해서 최근 체결가를 사용하면 되고,

호가창(orderbook)을 확인해서 매수호가 측의 가격을 사용할 건지 매도호가 측의 가격을 사용할 건지

캔들정보를 보고 OHLCV를 사용할 것인지,

또는 여러가지 정보를 사용해서 도출된 가격을 사용할 것인지

우리가 원하는 매매전략에 따라서 다양한 지정가가 나올 수 있습니다.

이 부분을 어떻게 구현하면 좋을까요?

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다