ksxt 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. ksxt/__pycache__/bithumb.cpython-312.pyc +0 -0
  2. ksxt/__pycache__/koreainvest.cpython-312.pyc +0 -0
  3. ksxt/__pycache__/upbit.cpython-312.pyc +0 -0
  4. ksxt/api/__pycache__/bithumb.cpython-312.pyc +0 -0
  5. ksxt/api/__pycache__/koreainvest.cpython-312.pyc +0 -0
  6. ksxt/api/__pycache__/upbit.cpython-312.pyc +0 -0
  7. ksxt/async_/__pycache__/bithumb.cpython-312.pyc +0 -0
  8. ksxt/async_/__pycache__/koreainvest.cpython-312.pyc +0 -0
  9. ksxt/async_/__pycache__/upbit.cpython-312.pyc +0 -0
  10. ksxt/async_/base/__pycache__/async_exchange.cpython-312.pyc +0 -0
  11. ksxt/async_/base/async_exchange.py +19 -17
  12. ksxt/async_/bithumb.py +41 -21
  13. ksxt/async_/koreainvest.py +80 -8
  14. ksxt/async_/upbit.py +58 -22
  15. ksxt/base/__pycache__/exchange.cpython-312.pyc +0 -0
  16. ksxt/base/__pycache__/rest_exchange.cpython-312.pyc +0 -0
  17. ksxt/base/exchange.py +33 -66
  18. ksxt/base/rest_exchange.py +28 -29
  19. ksxt/bithumb.py +33 -14
  20. ksxt/koreainvest.py +30 -8
  21. ksxt/models/__pycache__/transaction.cpython-312.pyc +0 -0
  22. ksxt/models/transaction.py +2 -0
  23. ksxt/parser/__pycache__/bithumb.cpython-312.pyc +0 -0
  24. ksxt/parser/__pycache__/koreainvest.cpython-312.pyc +0 -0
  25. ksxt/parser/__pycache__/parser.cpython-312.pyc +0 -0
  26. ksxt/parser/__pycache__/upbit.cpython-312.pyc +0 -0
  27. ksxt/parser/bithumb.py +102 -12
  28. ksxt/parser/koreainvest.py +66 -12
  29. ksxt/parser/parser.py +25 -7
  30. ksxt/parser/upbit.py +106 -16
  31. ksxt/upbit.py +48 -18
  32. {ksxt-1.0.3.dist-info → ksxt-1.0.5.dist-info}/METADATA +1 -1
  33. {ksxt-1.0.3.dist-info → ksxt-1.0.5.dist-info}/RECORD +36 -36
  34. {ksxt-1.0.3.dist-info → ksxt-1.0.5.dist-info}/WHEEL +1 -1
  35. {ksxt-1.0.3.dist-info → ksxt-1.0.5.dist-info}/LICENSE.txt +0 -0
  36. {ksxt-1.0.3.dist-info → ksxt-1.0.5.dist-info}/top_level.txt +0 -0
ksxt/parser/bithumb.py CHANGED
@@ -1,6 +1,6 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timezone
2
2
  from operator import attrgetter
3
- from typing import Dict, List
3
+ from typing import Dict, List, Optional
4
4
 
5
5
  from ksxt.models.transaction import (
6
6
  ClosedOrderHistory,
@@ -46,8 +46,22 @@ class BithumbParser(BaseParser):
46
46
  warning_code=False if safer.safe_string(market, "market_warning") == "NONE" else True,
47
47
  )
48
48
 
49
- def parse_historical_data(self, response: dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
49
+ def parse_historical_data(
50
+ self,
51
+ response: List[Dict],
52
+ symbol: str,
53
+ start: datetime | None = None,
54
+ end: datetime | None = None,
55
+ base_market: str = "KRW",
56
+ ) -> HistoricalDataInfo:
50
57
  ohlcv = [self._parse_ohlcva(_, base_market) for _ in response]
58
+
59
+ # Filter by start and end datetime
60
+ if start:
61
+ ohlcv = [data for data in ohlcv if data.datetime >= start]
62
+ if end:
63
+ ohlcv = [data for data in ohlcv if data.datetime <= end]
64
+
51
65
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
52
66
 
53
67
  security_name = symbol
@@ -160,9 +174,19 @@ class BithumbParser(BaseParser):
160
174
 
161
175
  return ask_data, bid_data
162
176
 
163
- def parse_balance(self, response: List[Dict], base_market: str = "KRW") -> BalanceInfo:
164
- # currency가 'KRW'가 아닌 항목만 필터링
165
- filtered_data = [data for data in response if data["currency"] != base_market]
177
+ def parse_balance(
178
+ self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
179
+ ) -> BalanceInfo:
180
+ # 1. Filter out entries where 'currency' is not the base_market
181
+ # 2. Ensure 'unit_currency' is the base_market
182
+ # 3. Exclude entries where 'currency' is in excluded_symbols
183
+ filtered_data = [
184
+ data
185
+ for data in response
186
+ if data["currency"] != base_market
187
+ and data["unit_currency"] == base_market
188
+ and (excluded_symbols is None or data["currency"] not in excluded_symbols)
189
+ ]
166
190
 
167
191
  # Initialize balance list
168
192
  balances = [self._parse_balance(data, base_market) for data in filtered_data]
@@ -196,8 +220,11 @@ class BithumbParser(BaseParser):
196
220
  )
197
221
 
198
222
  def parse_cash(self, response: List[Dict], base_market: str = "KRW") -> CashInfo:
199
- # currency가 'KRW' 항목만 필터링
200
- filtered_data = [data for data in response if data["currency"] == base_market]
223
+ # 1. Filter entries where 'currency' is equal to 'base_market'
224
+ # 2. Ensure 'unit_currency' is also equal to 'base_market'
225
+ filtered_data = [
226
+ data for data in response if data["currency"] == base_market and data["unit_currency"] == base_market
227
+ ]
201
228
 
202
229
  # 필터링된 데이터가 없으면 기본값으로 CashInfo 반환
203
230
  if not filtered_data:
@@ -243,8 +270,21 @@ class BithumbParser(BaseParser):
243
270
  market_ask_fee=safer.safe_number(response, "maker_ask_fee"),
244
271
  )
245
272
 
246
- def parse_open_order_history(self, response: dict, base_market: str = "KRW") -> OpenedOrderHistory:
273
+ def parse_open_order_history(
274
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
275
+ ) -> OpenedOrderHistory:
247
276
  orders = [self._parse_open_order_info(order, base_market) for order in response]
277
+
278
+ # Filter by start and end datetime
279
+ if start:
280
+ orders = [
281
+ order for order in orders if order.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
282
+ ]
283
+ if end:
284
+ orders = [
285
+ order for order in orders if order.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
286
+ ]
287
+
248
288
  return OpenedOrderHistory(history=orders)
249
289
 
250
290
  def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> OpenedOrderInfo:
@@ -255,13 +295,27 @@ class BithumbParser(BaseParser):
255
295
  price=safer.safe_number(order, "price"),
256
296
  qty=safer.safe_number(order, "volume"),
257
297
  amount=safer.safe_number(order, "price") * safer.safe_number(order, "volume"),
298
+ tax=0,
258
299
  fee=safer.safe_number(order, "reserved_fee"),
259
300
  created_at=datetime.fromisoformat(safer.safe_string(order, "created_at")),
260
301
  order_type=safer.safe_string(order, "ord_type"),
261
302
  )
262
303
 
263
- def parse_closed_order_history(self, response: dict, base_market: str = "KRW") -> ClosedOrderHistory:
304
+ def parse_closed_order_history(
305
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
306
+ ) -> ClosedOrderHistory:
264
307
  orders = [self._parse_closed_order_info(order, base_market) for order in response]
308
+
309
+ # Filter by start and end datetime
310
+ if start:
311
+ orders = [
312
+ order for order in orders if order.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
313
+ ]
314
+ if end:
315
+ orders = [
316
+ order for order in orders if order.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
317
+ ]
318
+
265
319
  return ClosedOrderHistory(history=orders)
266
320
 
267
321
  def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> ClosedOrderInfo:
@@ -272,6 +326,7 @@ class BithumbParser(BaseParser):
272
326
  price=safer.safe_number(order, "price"),
273
327
  qty=safer.safe_number(order, "volume"),
274
328
  amount=safer.safe_number(order, "price") * safer.safe_number(order, "volume"),
329
+ tax=0,
275
330
  fee=safer.safe_number(order, "reserved_fee"),
276
331
  created_at=datetime.fromisoformat(safer.safe_string(order, "created_at")),
277
332
  order_type=safer.safe_string(order, "ord_type"),
@@ -283,12 +338,46 @@ class BithumbParser(BaseParser):
283
338
  def parse_create_order(self, response: dict, base_market: str = "KRW") -> CreateOrderResponse:
284
339
  return CreateOrderResponse(order_datetime=datetime.now(), order_id=safer.safe_string(response, "uuid"))
285
340
 
286
- def parse_withdrawal_history(self, response: List[Dict], base_market: str = "KRW") -> WithdrawalHistory:
341
+ def parse_withdrawal_history(
342
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
343
+ ) -> WithdrawalHistory:
287
344
  parsed_items = [self._parse_transaction_history_item(item, base_market) for item in response]
345
+
346
+ # Filter by start and end datetime
347
+ if start:
348
+ parsed_items = [
349
+ item
350
+ for item in parsed_items
351
+ if item.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
352
+ ]
353
+ if end:
354
+ parsed_items = [
355
+ item
356
+ for item in parsed_items
357
+ if item.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
358
+ ]
359
+
288
360
  return WithdrawalHistory(history=parsed_items)
289
361
 
290
- def parse_deposit_history(self, response: List[Dict], base_market: str = "KRW") -> DepositHistory:
362
+ def parse_deposit_history(
363
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
364
+ ) -> DepositHistory:
291
365
  parsed_items = [self._parse_transaction_history_item(item, base_market) for item in response]
366
+
367
+ # Filter by start and end datetime
368
+ if start:
369
+ parsed_items = [
370
+ item
371
+ for item in parsed_items
372
+ if item.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
373
+ ]
374
+ if end:
375
+ parsed_items = [
376
+ item
377
+ for item in parsed_items
378
+ if item.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
379
+ ]
380
+
292
381
  return DepositHistory(history=parsed_items)
293
382
 
294
383
  def _parse_transaction_history_item(self, item: Dict, base_market: str = "KRW") -> TransactionInfo:
@@ -296,6 +385,7 @@ class BithumbParser(BaseParser):
296
385
  uuid=safer.safe_string(item, "uuid"),
297
386
  type=safer.safe_string(item, "type"),
298
387
  amount=safer.safe_number(item, "amount"),
388
+ tax=0,
299
389
  fee=safer.safe_number(item, "fee"),
300
390
  currency=safer.safe_string(item, "currency"),
301
391
  created_at=datetime.fromisoformat(safer.safe_string(item, "created_at")),
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from typing import Dict, List
2
+ from typing import Dict, List, Optional
3
3
 
4
4
  from ksxt.models.balance import BalanceData, BalanceInfo
5
5
  from ksxt.models.cash import CashInfo
@@ -52,9 +52,23 @@ class KoreaInvestParser(BaseParser):
52
52
  warning_code=safer.safe_boolean(market, " market_warning"),
53
53
  )
54
54
 
55
- def parse_historical_data(self, response: Dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
55
+ def parse_historical_data(
56
+ self,
57
+ response: List[Dict],
58
+ symbol: str,
59
+ start: datetime | None = None,
60
+ end: datetime | None = None,
61
+ base_market: str = "KRW",
62
+ ) -> HistoricalDataInfo:
56
63
  safe_response = safer.safe_value(response, "output2")
57
64
  ohlcv = [self._parse_ohlcva(_, base_market) for _ in safe_response]
65
+
66
+ # Filter by start and end datetime
67
+ if start:
68
+ ohlcv = [data for data in ohlcv if data.datetime >= start]
69
+ if end:
70
+ ohlcv = [data for data in ohlcv if data.datetime <= end]
71
+
58
72
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
59
73
 
60
74
  security_name = symbol
@@ -77,9 +91,23 @@ class KoreaInvestParser(BaseParser):
77
91
  acml_amount=safer.safe_number(ohlcva, "acml_tr_pbmn"),
78
92
  )
79
93
 
80
- def parse_historical_index_data(self, response: Dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
94
+ def parse_historical_index_data(
95
+ self,
96
+ response: Dict,
97
+ symbol: str,
98
+ start: datetime | None = None,
99
+ end: datetime | None = None,
100
+ base_market: str = "KRW",
101
+ ) -> HistoricalDataInfo:
81
102
  safe_response = safer.safe_value(response, "output2")
82
103
  ohlcv = [self._parse_index_ohlcva(_, base_market) for _ in safe_response]
104
+
105
+ # Filter by start and end datetime
106
+ if start:
107
+ ohlcv = [data for data in ohlcv if data.datetime >= start]
108
+ if end:
109
+ ohlcv = [data for data in ohlcv if data.datetime <= end]
110
+
83
111
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
84
112
 
85
113
  security_name = symbol
@@ -153,11 +181,17 @@ class KoreaInvestParser(BaseParser):
153
181
 
154
182
  return ask_data, bid_data
155
183
 
156
- def parse_balance(self, response: List[Dict], base_market: str = "KRW") -> BalanceInfo:
184
+ def parse_balance(
185
+ self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
186
+ ) -> BalanceInfo:
157
187
  safe_response = safer.safe_value(response, "output1")
158
188
 
159
- # Initialize balance list
160
- balances = [self._parse_balance(_, base_market) for _ in safe_response]
189
+ # Initialize balance list with filtering
190
+ balances = [
191
+ self._parse_balance(_, base_market)
192
+ for _ in safe_response
193
+ if excluded_symbols is None or safer.safe_string(_, "pdno") not in excluded_symbols
194
+ ]
161
195
 
162
196
  # 총 매입금액
163
197
  total_amount = sum(bal.price * bal.qty for bal in balances)
@@ -236,10 +270,19 @@ class KoreaInvestParser(BaseParser):
236
270
  market_ask_fee=0.0140527 + trading_fee,
237
271
  )
238
272
 
239
- def parse_closed_order_history(self, response: Dict, base_market: str = "KRW") -> ClosedOrderHistory:
273
+ def parse_closed_order_history(
274
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
275
+ ) -> ClosedOrderHistory:
240
276
  safe_response = safer.safe_value(response, "output1")
241
- history = [self._parse_closed_order_info(_, base_market) for _ in safe_response]
242
- return ClosedOrderHistory(history=history)
277
+ orders = [self._parse_closed_order_info(_, base_market) for _ in safe_response]
278
+
279
+ # Filter by start and end datetime
280
+ if start:
281
+ orders = [order for order in orders if order.created_at >= start]
282
+ if end:
283
+ orders = [order for order in orders if order.created_at <= end]
284
+
285
+ return ClosedOrderHistory(history=orders)
243
286
 
244
287
  def _parse_closed_order_info(self, order: Dict, base_market: str = "KRW") -> ClosedOrderInfo:
245
288
  if safer.safe_string(order, "sll_buy_dvsn_cd") == "01":
@@ -254,16 +297,26 @@ class KoreaInvestParser(BaseParser):
254
297
  price=safer.safe_number(order, "avg_prvs"),
255
298
  qty=safer.safe_number(order, "tot_ccld_qty"),
256
299
  amount=safer.safe_number(order, "tot_ccld_amt"),
300
+ tax=0,
257
301
  # 단일 주문 이력에는 수수료 관련 정보가 없고, 조회한 전체 목록에 대해 수수료 정보만 존재한다.
258
302
  fee=0,
259
303
  created_at=datetime.fromisoformat(safer.safe_string(order, "ord_dt")),
260
304
  order_type=safer.safe_string(order, "avg_prvs"),
261
305
  )
262
306
 
263
- def parse_open_order_history(self, response: Dict, base_market: str = "KRW") -> OpenedOrderHistory:
307
+ def parse_open_order_history(
308
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
309
+ ) -> OpenedOrderHistory:
264
310
  safe_response = safer.safe_value(response, "output1")
265
- history = [self._parse_open_order_info(_, base_market) for _ in safe_response]
266
- return OpenedOrderHistory(history=history)
311
+ orders = [self._parse_open_order_info(_, base_market) for _ in safe_response]
312
+
313
+ # Filter by start and end datetime
314
+ if start:
315
+ orders = [order for order in orders if order.created_at >= start]
316
+ if end:
317
+ orders = [order for order in orders if order.created_at <= end]
318
+
319
+ return OpenedOrderHistory(history=orders)
267
320
 
268
321
  def _parse_open_order_info(self, order: Dict, base_market: str = "KRW") -> OpenedOrderInfo:
269
322
  if safer.safe_string(order, "sll_buy_dvsn_cd") == "01":
@@ -278,6 +331,7 @@ class KoreaInvestParser(BaseParser):
278
331
  price=safer.safe_number(order, "avg_prvs"),
279
332
  qty=safer.safe_number(order, "tot_ccld_qty"),
280
333
  amount=safer.safe_number(order, "tot_ccld_amt"),
334
+ tax=0,
281
335
  # 단일 주문 이력에는 수수료 관련 정보가 없고, 조회한 전체 목록에 대해 수수료 정보만 존재한다.
282
336
  fee=0,
283
337
  created_at=datetime.fromisoformat(safer.safe_string(order, "ord_dt")),
ksxt/parser/parser.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import Dict, List
1
+ from datetime import datetime
2
+ from typing import Dict, List, Optional
2
3
  import inspect
3
4
 
4
5
  from ksxt.models.balance import BalanceData, BalanceInfo
@@ -30,7 +31,14 @@ class BaseParser:
30
31
  def _parse_market(self, market: Dict) -> SecurityData:
31
32
  self._raise_not_implemented_error()
32
33
 
33
- def parse_historical_data(self, response: dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
34
+ def parse_historical_data(
35
+ self,
36
+ response: List[Dict],
37
+ symbol: str,
38
+ start: datetime | None = None,
39
+ end: datetime | None = None,
40
+ base_market: str = "KRW",
41
+ ) -> HistoricalDataInfo:
34
42
  self._raise_not_implemented_error()
35
43
 
36
44
  def _parse_ohlcva(self, ohlcva: dict, base_market: str = "KRW") -> OHLCVData:
@@ -62,7 +70,9 @@ class BaseParser:
62
70
  ) -> tuple[OrderBookData, OrderBookData]:
63
71
  self._raise_not_implemented_error()
64
72
 
65
- def parse_balance(self, response: List[Dict], base_market: str = "KRW") -> BalanceInfo:
73
+ def parse_balance(
74
+ self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
75
+ ) -> BalanceInfo:
66
76
  self._raise_not_implemented_error()
67
77
 
68
78
  def _parse_balance(self, data: Dict, base_market: str = "KRW") -> BalanceData:
@@ -77,13 +87,17 @@ class BaseParser:
77
87
  def parse_trade_fee(self, response: dict, base_market: str = "KRW") -> TradeFeeInfo:
78
88
  self._raise_not_implemented_error()
79
89
 
80
- def parse_open_order_history(self, response: dict, base_market: str = "KRW") -> OpenedOrderHistory:
90
+ def parse_open_order_history(
91
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
92
+ ) -> OpenedOrderHistory:
81
93
  self._raise_not_implemented_error()
82
94
 
83
95
  def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> OpenedOrderInfo:
84
96
  self._raise_not_implemented_error()
85
97
 
86
- def parse_closed_order_history(self, response: dict, base_market: str = "KRW") -> ClosedOrderHistory:
98
+ def parse_closed_order_history(
99
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
100
+ ) -> ClosedOrderHistory:
87
101
  self._raise_not_implemented_error()
88
102
 
89
103
  def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> ClosedOrderInfo:
@@ -98,10 +112,14 @@ class BaseParser:
98
112
  def parse_create_order(self, response: dict, base_market: str = "KRW") -> CreateOrderResponse:
99
113
  self._raise_not_implemented_error()
100
114
 
101
- def parse_withdrawal_history(self, response: List[Dict], base_market: str = "KRW") -> WithdrawalHistory:
115
+ def parse_withdrawal_history(
116
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
117
+ ) -> WithdrawalHistory:
102
118
  self._raise_not_implemented_error()
103
119
 
104
- def parse_deposit_history(self, response: List[Dict], base_market: str = "KRW") -> DepositHistory:
120
+ def parse_deposit_history(
121
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
122
+ ) -> DepositHistory:
105
123
  self._raise_not_implemented_error()
106
124
 
107
125
  def _parse_transaction_history_item(self, item: Dict, base_market: str = "KRW") -> TransactionInfo:
ksxt/parser/upbit.py CHANGED
@@ -1,6 +1,6 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timezone
2
2
  from operator import attrgetter
3
- from typing import Dict, List
3
+ from typing import Dict, List, Optional
4
4
 
5
5
  from ksxt.models.balance import BalanceData, BalanceInfo
6
6
  from ksxt.models.cash import CashInfo
@@ -56,8 +56,22 @@ class UpbitParser(BaseParser):
56
56
  warning_code=safer.safe_boolean(market, " market_warning"),
57
57
  )
58
58
 
59
- def parse_historical_data(self, response: List[Dict], symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
59
+ def parse_historical_data(
60
+ self,
61
+ response: List[Dict],
62
+ symbol: str,
63
+ start: datetime | None = None,
64
+ end: datetime | None = None,
65
+ base_market: str = "KRW",
66
+ ) -> HistoricalDataInfo:
60
67
  ohlcv = [self._parse_ohlcva(_, base_market) for _ in response]
68
+
69
+ # Filter by start and end datetime
70
+ if start:
71
+ ohlcv = [data for data in ohlcv if data.datetime.astimezone(timezone.utc) >= start.astimezone(timezone.utc)]
72
+ if end:
73
+ ohlcv = [data for data in ohlcv if data.datetime.astimezone(timezone.utc) <= end.astimezone(timezone.utc)]
74
+
61
75
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
62
76
 
63
77
  security_name = symbol
@@ -166,9 +180,19 @@ class UpbitParser(BaseParser):
166
180
 
167
181
  return ask_data, bid_data
168
182
 
169
- def parse_balance(self, response: List[Dict], base_market: str = "KRW") -> BalanceInfo:
170
- # currency가 'KRW'가 아닌 항목만 필터링
171
- filtered_data = [data for data in response if data["currency"] != base_market]
183
+ def parse_balance(
184
+ self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
185
+ ) -> BalanceInfo:
186
+ # 1. Filter out entries where 'currency' is not the base_market
187
+ # 2. Ensure 'unit_currency' is the base_market
188
+ # 3. Exclude entries where 'currency' is in excluded_symbols
189
+ filtered_data = [
190
+ data
191
+ for data in response
192
+ if data["currency"] != base_market
193
+ and data["unit_currency"] == base_market
194
+ and (excluded_symbols is None or data["currency"] not in excluded_symbols)
195
+ ]
172
196
 
173
197
  # Initialize balance list
174
198
  balances = [self._parse_balance(data, base_market) for data in filtered_data]
@@ -202,8 +226,11 @@ class UpbitParser(BaseParser):
202
226
  )
203
227
 
204
228
  def parse_cash(self, response: List[Dict], base_market: str = "KRW") -> CashInfo:
205
- # currency가 'KRW' 항목만 필터링
206
- filtered_data = [data for data in response if data["currency"] == base_market]
229
+ # 1. Filter entries where 'currency' is equal to 'base_market'
230
+ # 2. Ensure 'unit_currency' is also equal to 'base_market'
231
+ filtered_data = [
232
+ data for data in response if data["currency"] == base_market and data["unit_currency"] == base_market
233
+ ]
207
234
 
208
235
  # 필터링된 데이터가 없으면 기본값으로 CashInfo 반환
209
236
  if not filtered_data:
@@ -249,8 +276,21 @@ class UpbitParser(BaseParser):
249
276
  market_ask_fee=safer.safe_number(response, "maker_ask_fee"),
250
277
  )
251
278
 
252
- def parse_closed_order_history(self, response: List[dict], base_market: str = "KRW") -> ClosedOrderHistory:
279
+ def parse_closed_order_history(
280
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
281
+ ) -> ClosedOrderHistory:
253
282
  orders = [self._parse_closed_order_info(order, base_market) for order in response]
283
+
284
+ # Filter by start and end datetime
285
+ if start:
286
+ orders = [
287
+ order for order in orders if order.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
288
+ ]
289
+ if end:
290
+ orders = [
291
+ order for order in orders if order.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
292
+ ]
293
+
254
294
  return ClosedOrderHistory(history=orders)
255
295
 
256
296
  def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> ClosedOrderInfo:
@@ -258,16 +298,30 @@ class UpbitParser(BaseParser):
258
298
  uuid=safer.safe_string(order, "uuid"),
259
299
  type=safer.safe_string(order, "side"),
260
300
  symbol=self.safe_symbol(base_market, safer.safe_string(order, "market")),
261
- price=safer.safe_number(order, "price"),
301
+ price=safer.safe_number(order, "executed_funds") / safer.safe_number(order, "volume"),
262
302
  qty=safer.safe_number(order, "volume"),
263
- amount=safer.safe_number(order, "price") * safer.safe_number(order, "volume"),
303
+ amount=safer.safe_number(order, "executed_funds"),
304
+ tax=0,
264
305
  fee=safer.safe_number(order, "paid_fee"), # Adjusted to 'paid_fee' to match provided data
265
306
  created_at=datetime.fromisoformat(safer.safe_string(order, "created_at")),
266
307
  order_type=safer.safe_string(order, "ord_type"),
267
308
  )
268
309
 
269
- def parse_open_order_history(self, response: List[dict], base_market: str = "KRW") -> OpenedOrderHistory:
310
+ def parse_open_order_history(
311
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
312
+ ) -> OpenedOrderHistory:
270
313
  orders = [self._parse_open_order_info(order, base_market) for order in response]
314
+
315
+ # Filter by start and end datetime
316
+ if start:
317
+ orders = [
318
+ order for order in orders if order.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
319
+ ]
320
+ if end:
321
+ orders = [
322
+ order for order in orders if order.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
323
+ ]
324
+
271
325
  return OpenedOrderHistory(history=orders)
272
326
 
273
327
  def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> OpenedOrderInfo:
@@ -275,20 +329,55 @@ class UpbitParser(BaseParser):
275
329
  uuid=safer.safe_string(order, "uuid"),
276
330
  type=safer.safe_string(order, "side"),
277
331
  symbol=self.safe_symbol(base_market, safer.safe_string(order, "market")),
278
- price=safer.safe_number(order, "price"),
332
+ price=safer.safe_number(order, "executed_funds") / safer.safe_number(order, "volume"),
279
333
  qty=safer.safe_number(order, "volume"),
280
- amount=safer.safe_number(order, "price") * safer.safe_number(order, "volume"),
334
+ amount=safer.safe_number(order, "executed_funds"),
335
+ tax=0,
281
336
  fee=safer.safe_number(order, "reserved_fee"),
282
337
  created_at=datetime.fromisoformat(safer.safe_string(order, "created_at")),
283
338
  order_type=safer.safe_string(order, "ord_type"),
284
339
  )
285
340
 
286
- def parse_withdrawal_history(self, response: List[Dict], base_market: str = "KRW") -> WithdrawalHistory:
341
+ def parse_withdrawal_history(
342
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
343
+ ) -> WithdrawalHistory:
287
344
  parsed_items = [self._parse_transaction_history_item(item, base_market) for item in response]
345
+
346
+ # Filter by start and end datetime
347
+ if start:
348
+ parsed_items = [
349
+ item
350
+ for item in parsed_items
351
+ if item.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
352
+ ]
353
+ if end:
354
+ parsed_items = [
355
+ item
356
+ for item in parsed_items
357
+ if item.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
358
+ ]
359
+
288
360
  return WithdrawalHistory(history=parsed_items)
289
361
 
290
- def parse_deposit_history(self, response: List[Dict], base_market: str = "KRW") -> DepositHistory:
362
+ def parse_deposit_history(
363
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
364
+ ) -> DepositHistory:
291
365
  parsed_items = [self._parse_transaction_history_item(item, base_market) for item in response]
366
+
367
+ # Filter by start and end datetime
368
+ if start:
369
+ parsed_items = [
370
+ item
371
+ for item in parsed_items
372
+ if item.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
373
+ ]
374
+ if end:
375
+ parsed_items = [
376
+ item
377
+ for item in parsed_items
378
+ if item.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
379
+ ]
380
+
292
381
  return DepositHistory(history=parsed_items)
293
382
 
294
383
  def _parse_transaction_history_item(self, item: Dict, base_market: str = "KRW") -> TransactionInfo:
@@ -296,6 +385,7 @@ class UpbitParser(BaseParser):
296
385
  uuid=safer.safe_string(item, "uuid"),
297
386
  type=safer.safe_string(item, "type"),
298
387
  amount=safer.safe_number(item, "amount"),
388
+ tax=0,
299
389
  fee=safer.safe_number(item, "fee"),
300
390
  currency=safer.safe_string(item, "currency"),
301
391
  created_at=datetime.fromisoformat(safer.safe_string(item, "done_at")),