-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 매수 주문 최소금액 클라이언트 검증 옵션 추가 #44
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,8 +1,12 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from decimal import Decimal | ||
| from typing import Any | ||
|
|
||
| from upbeat._auth import Credentials | ||
| from upbeat._base import _AsyncAPIResource, _SyncAPIResource | ||
| from upbeat._errors import ValidationError | ||
| from upbeat._http import AsyncTransport, SyncTransport | ||
| from upbeat.types.order import ( | ||
| CancelAndNewOrderResponse, | ||
| CancelResult, | ||
|
|
@@ -20,7 +24,54 @@ def _filter_params(**kwargs: Any) -> dict[str, Any]: | |
| return {k: v for k, v in kwargs.items() if v is not None} | ||
|
|
||
|
|
||
| def _compute_bid_total( | ||
| price: str | None, volume: str | None, ord_type: str | ||
| ) -> Decimal | None: | ||
| """Return the total KRW value of a bid order, or None if indeterminate.""" | ||
| if price is None: | ||
| return None | ||
| if ord_type == "limit": | ||
| return Decimal(price) * Decimal(volume) if volume is not None else None | ||
| return Decimal(price) | ||
|
|
||
|
|
||
| class OrdersAPI(_SyncAPIResource): | ||
| _validate_min_order: bool | ||
|
|
||
| def __init__( | ||
| self, | ||
| transport: SyncTransport, | ||
| credentials: Credentials | None, | ||
| *, | ||
| validate_min_order: bool = False, | ||
| ) -> None: | ||
| super().__init__(transport, credentials) | ||
| self._validate_min_order = validate_min_order | ||
|
|
||
| def _check_min_order( | ||
| self, | ||
| market: str, | ||
| side: str, | ||
| price: str | None, | ||
| volume: str | None, | ||
| ord_type: str, | ||
| ) -> None: | ||
| if not self._validate_min_order or side != "bid": | ||
| return | ||
| total = _compute_bid_total(price, volume, ord_type) | ||
| if total is None: | ||
| 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. 매 주문마다
|
||
| if chance.market.bid is not None: | ||
| min_total = Decimal(chance.market.bid.min_total) | ||
| if total < min_total: | ||
| raise ValidationError( | ||
| f"Order total {total} is below minimum {min_total} for {market}", | ||
| market=market, | ||
| total=str(total), | ||
| min_total=chance.market.bid.min_total, | ||
| ) | ||
|
|
||
| def create( | ||
| self, | ||
| *, | ||
|
|
@@ -33,6 +84,7 @@ def create( | |
| time_in_force: str | None = None, | ||
| smp_type: str | None = None, | ||
| ) -> OrderCreated: | ||
| self._check_min_order(market, side, price, volume, ord_type) | ||
| json_body = _filter_params( | ||
| market=market, | ||
| side=side, | ||
|
|
@@ -60,6 +112,7 @@ def create_test( | |
| time_in_force: str | None = None, | ||
| smp_type: str | None = None, | ||
| ) -> OrderCreated: | ||
| self._check_min_order(market, side, price, volume, ord_type) | ||
| json_body = _filter_params( | ||
| market=market, | ||
| side=side, | ||
|
|
@@ -244,6 +297,42 @@ def get_chance(self, *, market: str) -> OrderChance: | |
|
|
||
|
|
||
| class AsyncOrdersAPI(_AsyncAPIResource): | ||
| _validate_min_order: bool | ||
|
|
||
| def __init__( | ||
| self, | ||
| transport: AsyncTransport, | ||
| credentials: Credentials | None, | ||
| *, | ||
| validate_min_order: bool = False, | ||
| ) -> None: | ||
| super().__init__(transport, credentials) | ||
| self._validate_min_order = validate_min_order | ||
|
|
||
| async def _check_min_order( | ||
| self, | ||
| market: str, | ||
| side: str, | ||
| price: str | None, | ||
| volume: str | None, | ||
| ord_type: str, | ||
| ) -> None: | ||
| if not self._validate_min_order or side != "bid": | ||
| return | ||
| total = _compute_bid_total(price, volume, ord_type) | ||
| if total is None: | ||
| return | ||
| chance = await self.get_chance(market=market) | ||
| if chance.market.bid is not None: | ||
| min_total = Decimal(chance.market.bid.min_total) | ||
| if total < min_total: | ||
| raise ValidationError( | ||
| f"Order total {total} is below minimum {min_total} for {market}", | ||
| market=market, | ||
| total=str(total), | ||
| min_total=chance.market.bid.min_total, | ||
| ) | ||
|
|
||
| async def create( | ||
| self, | ||
| *, | ||
|
|
@@ -256,6 +345,7 @@ async def create( | |
| time_in_force: str | None = None, | ||
| smp_type: str | None = None, | ||
| ) -> OrderCreated: | ||
| await self._check_min_order(market, side, price, volume, ord_type) | ||
| json_body = _filter_params( | ||
| market=market, | ||
| side=side, | ||
|
|
@@ -283,6 +373,7 @@ async def create_test( | |
| time_in_force: str | None = None, | ||
| smp_type: str | None = None, | ||
| ) -> OrderCreated: | ||
| await self._check_min_order(market, side, price, volume, ord_type) | ||
| json_body = _filter_params( | ||
| market=market, | ||
| side=side, | ||
|
|
||
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.
👍
volume is not None가드가 잘 되어있어Decimal(None)호출이 방지됩니다.price에 잘못된 문자열이 올 경우Decimal의InvalidOperation이 발생하는데, 이는 호출자 입력 오류이므로 현재 동작(예외 전파)이 합리적입니다.🤖 Claude Code
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.
감사합니다. 말씀대로 잘못된 문자열 입력 시
InvalidOperation예외 전파가 호출자 책임이라 별도 처리 없이 두었습니다.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.
동의합니다. 👍
🤖 Claude Code