
백테스트 엔진을 라이브러리 크레이트로 분리했다. 백테스트 바이너리 크레이트를 워크스페이스 멤버로 생성하여 테스트를 진행하였다. 터미널에서 로그가 길어지면 짤리는 이슈가 있어서 파일로 저장하기로 했다.
async fn ready_for_backtest() -> anyhow::Result<()> {
let ema_double_filter_strategy = EmaDoubleFilterStrategy::new(500);
// db init
let connection_string = *;
let db_client = DbClient::new(connection_string).await?;
// 2년치 데이터 (1분봉)
// 6개월 warmup + 1년 6개월 백테스트
let total_candles = 60 * 24 * 30* 2; // 1년
let warmup_candles = 60 * 24 * 30 ; // 6개월 warmup
let res = trading_core::backtest::run_backtest(
&db_client,
"BTCUSDT",
Box::new(ema_double_filter_strategy),
total_candles,
100.0,
Some(warmup_candles),
).await?;
trading_core::backtest::print_report(&res);
Ok(())
}
db를 초기화하고 백테스트 기간을 설정한다. warmup기간도 설정해준다.
warmup 기간은 지표를 세팅하는 기간이다. 이번 백테스트에서 사용한 지표는 ema라서 위처럼 긴 시간은 필요 없을 것 같다.
백테스트를 돌렸더니 거래 통계가 0으로 나왔다. 파일 로그를 확인해보니 분명 매수 signal이 발생했는데 어떠한 거래도 발생하지 않았다. 분명 로직에서 이상이 있을 것이라 생각해 코드를 살펴보았다. FSM(Finite State Machine) 상태 이상 패턴을 통해 구현 중인데 StrategyState::InPosition 상태로 변할 수 없는 것을 확인했다.
StrategyState::ReadyForEntry { point_2_price, .. } => {
if self.does_price_touch(snapshot.candle.low, snapshot.ema54) {
info!(
"[{}] 💰 진입 신호 | #{:08x} | Price: {:.2}",
snapshot.candle.open_time.format("%Y-%m-%d %H:%M"),
self.patterns[pattern_idx].id,
snapshot.candle.close
);
let signal = Signal::Entry {
price: snapshot.ema54,
time: snapshot.candle.open_time,
reason: "모든 조건 충족 후 가격이 EMA54에 터치".to_string(),
};
self.patterns[pattern_idx].state = StrategyState::InPosition {
entry_price: snapshot.ema54,
entry_time: snapshot.candle.open_time,
entry_idx: snapshot.idx,
point_2_price: *point_2_price,
};
return PatternResult::Signal(signal);
}
if self.is_bearish_cross(prev_ema54, prev_ema102, snapshot.ema54, snapshot.ema102)
&& snapshot.candle.close < *point_2_price
{
info!(
"[{}] 🛑 손절 조건 발생 | #{:08x}",
snapshot.candle.open_time.format("%Y-%m-%d %H:%M"),
self.patterns[pattern_idx].id
);
PatternResult::Failed
} else {
PatternResult::Active
}
}
진입 신호가 발생 후 매수 Signal을 반환하는데, Signal값이 Entry인 경우 State를 지워버려서 생기는 문제였다. 익절 or 손절인 경우에만 State를 삭제하도록 했다. 다시 빌드 후 실행 결과 문제없이 동작을 하였다.
약 30일 정도 백테스틀 진행하였는데 결과는 다음과 같다.

총 수익은 $9.54인 것을 확인할 수 있다. 성능이 좋은 알고리즘은 아닌 것 같다.
위 백테스트 결과로 손익비는 약 2.212이다. 손익분기 승률이 31%는 되어야 본전이라고 한다. 운이 좋아서 수익으로 마감한거지 지속적으로 거래했다면 계좌는 우하향하는 전략이다.
앞으로 개선해야할 것들이 많다. 알고리즘도 좀 더 정교하게 손을 봐야한다. 예상으로는 청산하는 로직을 너무 대충 만들어서 그런걸 수 있다고 생각한다. 시간을 좀 더 쏟아야할 것 같다.