Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a Cryptocurrency Trading System

Building a Cryptocurrency Trading System

By Will

Buzzvil

June 02, 2021
Tweet

More Decks by Buzzvil

Other Decks in Programming

Transcript

  1. 이 주제를 선택한 이유 1. 데이터 중심 어플리케이션 설계에 관한

    주제 ❌ 2. 버즈빌에서 일하며 배운 것 ➡ 빌링서비스 구축기 ❌ 3. 개인적으로 개발했던 경험기👌 a. 축구 기록 관리 웹 어플리케이션 운영 경험 ❌ b. 부동산 데이터 분석 어플리케이션 개발 경험 ❌ c. 암호화폐 자동 매매 알고리즘 개발 경험 👌
  2. System Trading을 개발한 계기 1. 2017년 갑자기 전회사 개발팀으로 업무

    변경 - 업무 : Java - 관심 : Python 2. 회사에 Python을 전파하고자 하였으나 실패 3. 그냥 혼자 Python 프로젝트를 만들자 4. 암호화폐 Boom 5. 은행이율보다 높은 수익을 내는 뭔가를 만들자 - 연이율10% ?? / 월이율 1% ?? 6. 2017년 추석 연휴
  3. 알고리즘 v1 개요 1. 가격 하락시 매수 ➡ 조금 오르면

    매도 2. 매수 했는데 계속 하락 ➡ 손절 매 수 매 도 매 수 매 도 매 수 매 도 매 수 손 절
  4. 문제 1. 완벽한 타이밍을 맞추기 쉽지 않다.➡ 거의 불가능 2.

    손절이 많이 발생해서 이익을 상쇄 시켜 버린다.
  5. 알고리즘 v2 개요 1. 가격 하락시 매수 ➡ 조금 오르면

    매도 2. 매수 했는데 계속 하락 ➡ 또 다시 매수(매도시점을 앞당김) ➡ 이익이 발생하는 가격이 오면 매도 🔄 반복 매 수 매 도 매 수 매 도 매 수 매 도 매 수 매 수 매 도
  6. 리스크 1. 암호화폐가 없어진다. 2. 거래소 폐쇄 또는 암호화폐 거래

    금지 등 3. 매수한 암호화폐가 가격이 떨어지기만 한 후 영원히 혹은 몇년간 상승하지 않거나 사라진다.
  7. 리스크 1. 암호화폐 자체가 없어진다. ➡ 없어질 확률은 거의 없을

    것이다. 2. 거래소 폐쇄 또는 암호화폐 거래 금지 등 ➡ 약간의 리스크는 있지만 현재 세계적인 추세로는 폐쇄나 금지가 발생하지 않을 것이다. 3. 매수한 암호화폐가 가격이 떨어지기만 한 후 영원히 혹은 몇년간 상승하지 않거나 사라진다. ➡ 신뢰할 수 있는 종류만 매매하도록 한다.
  8. 알고리즘 세부사항 진화 과정 1. Is_buy_timing - 현재 시장의 추세를

    분석 - 분석된 추세에 따라 매수 시점을 결정 2. sell_order - 박리 다매 - 조금 오르면 매도(예:0.1%) 🔄 반복
  9. 문제 1. Make_buy_order 를 했는데 매수가 안되고 가격이 상승 -

    하나도 체결이 안되는 경우 - 일부만 체결이 된 경우
  10. 알고리즘 진화 1. 매수 도중에 가격이 오르면 매수 주문을 취소!

    2. 취소후에 보유자산이 있으면 평균단가에 적정한 이율을 적용하여 매도! 3. 없으면 다시 시장 추세 분석!
  11. 알고리즘 진화 1. 매도 도중 가격이 하락 하는 경우 추가

    매수 할 수 있는 예산이 있는지 확인 2. 예산이 있다면 매도 주문을 취소! 3. 추가 매수 주문 생성!
  12. code def process(): api = API() data = None while

    not is_buy_timing(data): # analysis data from api data = api.analysis() buy_order = api.make_buy_order() while not buy_order.is_completed(): current_price = api.get_current_price() if current_price > buy_order.price * 1.01: api.cancel_order(buy_order) if api.get_balance() > 0: break else: return 0 # profit is zero sell_order = api.make_sell_order() while not sell_order.is_completed(): current_price = api.get_current_price() if current_price < sell_order.price * 0.9: if has_budget(): api.cancel_order(sell_order) buy_order_2 = api.make_buy_order() while not buy_order_2.is_completed(): ... profit = sell_order.funds - buy_order.funds return profit
  13. 누더기 코드 - 극악의 디버깅 난이도 - 코드 수정 거의

    불가능 - 나중에 코드 내용 파악 거의 불가능 - 각종 에러 대응 거의 불가능 - Logic 에러 - Api 에러 - 네트워크 에러 - 알 수 없는 에러 - 등등등...
  14. 아키텍처를 도입하자 Domain Layer - 나를 위해 24시간 일하는 Worker

    - 분석담당 Worker(Waiting) - 매수담당 Worker(Buying) - 매도담당 Worker(Selling) - Worker는 공통적으로 work라는 행동을 하는데 각자의 work방식은 고유하다. - Worker는 일할때 필요한 api(인터페이스)가 반드시 필요하다. - 일할 때 필요한 각종 데이터도 가지고 일한다.(budget, market, orders 등)
  15. 아키텍처를 도입하자 Service Layer • 언제까지 추가매수? ◦ 예산이 남아있을때

    까지! • 예산 분배 전략이 중요! • 추가 매수하는 시점이 중요! ex) 예산: 20,000원 1차: 100원/1000원어치: 101원/10원 2차: 96원/1000원어치: 99원/20원 3차: 93원/1000원어치: 97원/30원 4차: 73원/3500원어치: 85원/190원 5차: 58원/3500원어치: 74원/295원 6차: 26원/5000원어치: 45원/2250원 7차: 12원/5000원어치: 30원/3000원
  16. code def work(): while True: if worker := WorkerRepository.get_oldest_worker(): try:

    worker.work() WorkerRepository.update(worker) if worker.status == 'end': new_worker = ... WorkerRepository.add(new_worker) send_alarm(...) except domain_model.ApiError as e: send_alarm(f'API Error: ...") except Exception as e: send_alarm(f'Exception: ...") Service Layer
  17. code class Worker(abc.ABC): def __init__(self, api: AbstractApi ...): self.api =

    api ... @abc.abstractmethod def work(self): raise NotImplementedError def buy_order(self, price: float) -> Order: ... order = self.api.buy_order(price, self.budget) ... def sell_order(self, price: float) -> Order: ... def cancel_order(self, order: Order): ... def update_from_api(self): ... Domain Layer
  18. code Domain Layer class WaitingWorker(Worker): def work(self): if self._is_buy_timing(): order

    = self.buy_order(self.trade_price) self.status = WorkerStatus.BUYING self.orders.append(order) def _is_buy_timing(self): trend = self._decide_trend() if self.trade_price < self.waiting_price: if trend == MarketTrend.OVERHEATING: return self.trade_price < self.short_average * SHORT_TIMING ... elif trend == MarketTrend.NORMAL: return ... elif trend == MarketTrend.FROZEN: return ... return False def _decide_trend(self): if self.trade_price > self.long_average * (...): return MarketTrend.OVERHEATING elif ...: return MarketTrend.NORMAL elif ...: return MarketTrend.FROZEN else return MarketTrend.DANGER
  19. code Domain Layer class BuyingWorker(Worker): def work(self): latest_order = self.orders.get_latest_order()

    sell_rate = self.get_sell_rate() if latest_order.status == OrderStatus.WAIT: if self.trade_price >= self.orders.avg_buy_price * sell_rate: self.cancel_order(latest_order) else: # cancel or done if self.balance > 0: order = self.sell_order(self.orders.avg_buy_price * sell_rate) self.status = WorkerStatus.SELLING self.orders.append(order) else: self.status = WorkerStatus.WAITING
  20. code Domain Layer class SellingWorker(Worker): def work(self): latest_order = self.orders.get_latest_order()

    if latest_order.status == OrderStatus.WAIT: if self.budget != EMPTY and self._is_additional_buy_timing(): self.cancel_order(latest_order) else: # cancel or done if wait_orders := self.orders.get_wait_orders(): for o in wait_orders: self.cancel_order(o) else: if self.balance > 0: order = self.buy_order(self.next_price) self.status = WorkerStatus.BUYING self.orders.append(order) else: self.revenue = self.orders.get_sell_funds - self.orders.get_buy_funds self.status = WorkerStatus.END
  21. code Domain Layer class AbstractApi(abc.ABC): def __init__(self, market: Market): self.market

    = market @abc.abstractmethod def buy_order(self, price: float, budget: float) -> Order: raise NotImplementedError @abc.abstractmethod def sell_order(self, price: float, volume: float) -> Order: raise NotImplementedError @abc.abstractmethod def cancel_order(self, order_id: str) -> str: raise NotImplementedError @abc.abstractmethod def get_order(self, order_id: str) -> Order: raise NotImplementedError @abc.abstractmethod def get_orders(self, order_ids: List[str]=None) -> List[Order]: raise NotImplementedError @abc.abstractmethod def get_prices(self, unit: str='minute', counts: int=1, to: datetime=None) -> List[Price]: raise NotImplementedError @abc.abstractmethod def get_balance(self) -> Balance: raise NotImplementedError
  22. code Repository class Worker(models.Model): ... def to_domain(self, api: AbstractApi) ->

    domain_model.Worker: work_type = domain_model.Work if self.status == WorkerStatus.WAITING: work_type = domain_model.WaitingWorker elif self.status == WorkerStatus.BUYING: work_type = domain_model.BuyingWorker elif self.status == WorkerStatus.SELLING: work_type = domain_model.SellingWorker w = work_type( api=api(self.market), status=self.status, ... ) return w
  23. 결과 Was it successful? - 6.42% - Not bad 20-11

    20-12 21-01 21-02 21-03 21-04 21-05 0.8% 1.5% 0.3% 0.5% 0.45% 0.67% 2.2%
  24. 결론 - 투자는 없어도 되는 돈으로 - 주식에도 적용 -

    클린아키텍처 & DDD - 끈질기게 계속 하면 된다.