-
Notifications
You must be signed in to change notification settings - Fork 0
feat: get_chance() 결과 per-market TTL 캐시 추가 #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import time | ||
| from decimal import Decimal | ||
| from typing import Any | ||
|
|
||
|
|
@@ -35,6 +36,9 @@ def _compute_bid_total( | |
| return Decimal(price) | ||
|
|
||
|
|
||
| _DEFAULT_MIN_TOTAL_TTL: float = 60.0 | ||
|
|
||
|
|
||
| class OrdersAPI(_SyncAPIResource): | ||
| _validate_min_order: bool | ||
|
|
||
|
|
@@ -44,9 +48,21 @@ def __init__( | |
| credentials: Credentials | None, | ||
| *, | ||
| validate_min_order: bool = False, | ||
| min_total_ttl: float = _DEFAULT_MIN_TOTAL_TTL, | ||
| ) -> None: | ||
| super().__init__(transport, credentials) | ||
| self._validate_min_order = validate_min_order | ||
| self._min_total_ttl = min_total_ttl | ||
| self._min_total_cache: dict[str, tuple[str, float]] = {} | ||
|
|
||
| def _get_cached_min_total(self, market: str) -> str | None: | ||
| entry = self._min_total_cache.get(market) | ||
| if entry is not None: | ||
| value, expiry = entry | ||
| if time.monotonic() < expiry: | ||
| return value | ||
| del self._min_total_cache[market] | ||
| return None | ||
|
|
||
| def _check_min_order( | ||
| self, | ||
|
|
@@ -61,8 +77,25 @@ def _check_min_order( | |
| total = _compute_bid_total(price, volume, ord_type) | ||
| if total is None: | ||
| return | ||
|
|
||
| cached = self._get_cached_min_total(market) | ||
| if cached is not None: | ||
| min_total = Decimal(cached) | ||
| if total < min_total: | ||
| raise ValidationError( | ||
| f"Order total {total} is below minimum {min_total} for {market}", | ||
| market=market, | ||
| total=str(total), | ||
| min_total=cached, | ||
| ) | ||
| return | ||
|
|
||
| chance = self.get_chance(market=market) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [note] 사용자가 최상위 클라이언트를 통해 TTL을 설정할 수 없으므로, 의도적이라면 괜찮지만 그렇지 않다면
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의도적으로 내부 디테일로 둔 것입니다. TTL은 대부분의 사용자에게 기본값(60초)으로 충분하고, 고급 사용자는
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의도된 설계라면 충분히 납득됩니다. 기본값 60초면 대부분의 시나리오에서 적절하고, 고급 사용자용 escape hatch가 이미 있으니 현재 수준으로 충분합니다. Reviewed by Claude (Opus 4.6) — AI Code Reviewer |
||
| if chance.market.bid is not None: | ||
| self._min_total_cache[market] = ( | ||
| chance.market.bid.min_total, | ||
| time.monotonic() + self._min_total_ttl, | ||
| ) | ||
| min_total = Decimal(chance.market.bid.min_total) | ||
| if total < min_total: | ||
| raise ValidationError( | ||
|
|
@@ -305,9 +338,21 @@ def __init__( | |
| credentials: Credentials | None, | ||
| *, | ||
| validate_min_order: bool = False, | ||
| min_total_ttl: float = _DEFAULT_MIN_TOTAL_TTL, | ||
| ) -> None: | ||
| super().__init__(transport, credentials) | ||
| self._validate_min_order = validate_min_order | ||
| self._min_total_ttl = min_total_ttl | ||
| self._min_total_cache: dict[str, tuple[str, float]] = {} | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [duplication] 캐시 로직 자체는 순수 동기 연산이므로 ( class _MinTotalCacheMixin:
_min_total_ttl: float
_min_total_cache: dict[str, tuple[str, float]]
def _init_cache(self, ttl: float) -> None:
self._min_total_ttl = ttl
self._min_total_cache = {}
def _get_cached_min_total(self, market: str) -> str | None:
...단, 이 정도 중복이면 현재 수준도 충분히 수용 가능합니다. 향후 캐시 로직이 복잡해질 때 리팩토링해도 늦지 않습니다.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동의합니다. 현재 수준에서는 중복이 크지 않으므로 그대로 유지하고, 향후 캐시 로직이 복잡해지면 mixin으로 리팩토링하겠습니다.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동의합니다. 현 수준의 중복은 수용 가능하고, 캐시 정책이 복잡해질 때 리팩토링하는 판단이 합리적입니다. Reviewed by Claude (Opus 4.6) — AI Code Reviewer |
||
|
|
||
| def _get_cached_min_total(self, market: str) -> str | None: | ||
| entry = self._min_total_cache.get(market) | ||
| if entry is not None: | ||
| value, expiry = entry | ||
| if time.monotonic() < expiry: | ||
| return value | ||
| del self._min_total_cache[market] | ||
| return None | ||
|
|
||
| async def _check_min_order( | ||
| self, | ||
|
|
@@ -322,8 +367,25 @@ async def _check_min_order( | |
| total = _compute_bid_total(price, volume, ord_type) | ||
| if total is None: | ||
| return | ||
|
|
||
| cached = self._get_cached_min_total(market) | ||
| if cached is not None: | ||
| min_total = Decimal(cached) | ||
| if total < min_total: | ||
| raise ValidationError( | ||
| f"Order total {total} is below minimum {min_total} for {market}", | ||
| market=market, | ||
| total=str(total), | ||
| min_total=cached, | ||
| ) | ||
| return | ||
|
|
||
| chance = await self.get_chance(market=market) | ||
| if chance.market.bid is not None: | ||
| self._min_total_cache[market] = ( | ||
| chance.market.bid.min_total, | ||
| time.monotonic() + self._min_total_ttl, | ||
| ) | ||
| min_total = Decimal(chance.market.bid.min_total) | ||
| if total < min_total: | ||
| raise ValidationError( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[suggestion] 만료된 캐시 엔트리가
dict에서 제거되지 않습니다._get_cached_min_total에서 만료 감지 시del self._min_total_cache[market]로 정리하면 장기 실행 프로세스에서 불필요한 메모리 점유를 방지할 수 있습니다.물론 실제 마켓 수가 유한하므로 실질적인 문제는 아니지만, 방어적으로 정리하는 게 깔끔합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반영했습니다. 만료 감지 시
del self._min_total_cache[market]로 엔트리를 정리하도록 수정했습니다. (51d49b3)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다. 동기/비동기 양쪽 모두
del self._min_total_cache[market]가 추가된 것을 확인했습니다. (51d49b3)Reviewed by Claude (Opus 4.6) — AI Code Reviewer