ksxt 1.0.4__py3-none-any.whl → 1.0.6__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 (75) hide show
  1. ksxt/__pycache__/__init__.cpython-312.pyc +0 -0
  2. ksxt/__pycache__/bithumb.cpython-312.pyc +0 -0
  3. ksxt/__pycache__/koreainvest.cpython-312.pyc +0 -0
  4. ksxt/__pycache__/upbit.cpython-312.pyc +0 -0
  5. ksxt/api/__pycache__/__init__.cpython-312.pyc +0 -0
  6. ksxt/api/__pycache__/bithumb.cpython-312.pyc +0 -0
  7. ksxt/api/__pycache__/koreainvest.cpython-312.pyc +0 -0
  8. ksxt/api/__pycache__/upbit.cpython-312.pyc +0 -0
  9. ksxt/async_/__pycache__/__init__.cpython-312.pyc +0 -0
  10. ksxt/async_/__pycache__/bithumb.cpython-312.pyc +0 -0
  11. ksxt/async_/__pycache__/koreainvest.cpython-312.pyc +0 -0
  12. ksxt/async_/__pycache__/upbit.cpython-312.pyc +0 -0
  13. ksxt/async_/base/__pycache__/__init__.cpython-312.pyc +0 -0
  14. ksxt/async_/base/__pycache__/async_exchange.cpython-312.pyc +0 -0
  15. ksxt/async_/base/async_exchange.py +23 -17
  16. ksxt/async_/bithumb.py +56 -21
  17. ksxt/async_/koreainvest.py +87 -8
  18. ksxt/async_/upbit.py +67 -24
  19. ksxt/base/__pycache__/__init__.cpython-312.pyc +0 -0
  20. ksxt/base/__pycache__/errors.cpython-312.pyc +0 -0
  21. ksxt/base/__pycache__/exchange.cpython-312.pyc +0 -0
  22. ksxt/base/__pycache__/rate_limiter.cpython-312.pyc +0 -0
  23. ksxt/base/__pycache__/rest_exchange.cpython-312.pyc +0 -0
  24. ksxt/base/__pycache__/types.cpython-312.pyc +0 -0
  25. ksxt/base/exchange.py +42 -23
  26. ksxt/base/rest_exchange.py +32 -28
  27. ksxt/bithumb.py +48 -14
  28. ksxt/config/__pycache__/__init__.cpython-312.pyc +0 -0
  29. ksxt/config/token.toml +3 -7
  30. ksxt/koreainvest.py +37 -8
  31. ksxt/market/__pycache__/base.cpython-312.pyc +0 -0
  32. ksxt/market/__pycache__/db.cpython-312.pyc +0 -0
  33. ksxt/market/__pycache__/logging.cpython-312.pyc +0 -0
  34. ksxt/market/__pycache__/manager.cpython-312.pyc +0 -0
  35. ksxt/market/__pycache__/markets.cpython-312.pyc +0 -0
  36. ksxt/market/krx/__pycache__/kosdaq.cpython-312.pyc +0 -0
  37. ksxt/market/krx/__pycache__/kospi.cpython-312.pyc +0 -0
  38. ksxt/market/krx/__pycache__/stock.cpython-312.pyc +0 -0
  39. ksxt/market/us/__pycache__/amex.cpython-312.pyc +0 -0
  40. ksxt/market/us/__pycache__/nasdaq.cpython-312.pyc +0 -0
  41. ksxt/market/us/__pycache__/nyse.cpython-312.pyc +0 -0
  42. ksxt/market/us/__pycache__/stock.cpython-312.pyc +0 -0
  43. ksxt/models/__pycache__/__init__.cpython-312.pyc +0 -0
  44. ksxt/models/__pycache__/balance.cpython-312.pyc +0 -0
  45. ksxt/models/__pycache__/cash.cpython-312.pyc +0 -0
  46. ksxt/models/__pycache__/common.cpython-312.pyc +0 -0
  47. ksxt/models/__pycache__/error.cpython-312.pyc +0 -0
  48. ksxt/models/__pycache__/historical.cpython-312.pyc +0 -0
  49. ksxt/models/__pycache__/market.cpython-312.pyc +0 -0
  50. ksxt/models/__pycache__/order.cpython-312.pyc +0 -0
  51. ksxt/models/__pycache__/orderbook.cpython-312.pyc +0 -0
  52. ksxt/models/__pycache__/ticker.cpython-312.pyc +0 -0
  53. ksxt/models/__pycache__/token.cpython-312.pyc +0 -0
  54. ksxt/models/__pycache__/transaction.cpython-312.pyc +0 -0
  55. ksxt/models/balance.py +17 -4
  56. ksxt/models/transaction.py +22 -28
  57. ksxt/parser/__pycache__/bithumb.cpython-312.pyc +0 -0
  58. ksxt/parser/__pycache__/koreainvest.cpython-312.pyc +0 -0
  59. ksxt/parser/__pycache__/parser.cpython-312.pyc +0 -0
  60. ksxt/parser/__pycache__/upbit.cpython-312.pyc +0 -0
  61. ksxt/parser/bithumb.py +124 -23
  62. ksxt/parser/koreainvest.py +84 -21
  63. ksxt/parser/parser.py +30 -10
  64. ksxt/parser/upbit.py +134 -27
  65. ksxt/upbit.py +55 -18
  66. ksxt/utils/__pycache__/safer.cpython-312.pyc +0 -0
  67. ksxt/utils/__pycache__/sorter.cpython-312.pyc +0 -0
  68. ksxt/utils/__pycache__/timer.cpython-312.pyc +0 -0
  69. {ksxt-1.0.4.dist-info → ksxt-1.0.6.dist-info}/METADATA +1 -1
  70. ksxt-1.0.6.dist-info/RECORD +119 -0
  71. {ksxt-1.0.4.dist-info → ksxt-1.0.6.dist-info}/WHEEL +1 -1
  72. ksxt/async_/base/__pycache__/throttler.cpython-312.pyc +0 -0
  73. ksxt-1.0.4.dist-info/RECORD +0 -120
  74. {ksxt-1.0.4.dist-info → ksxt-1.0.6.dist-info}/LICENSE.txt +0 -0
  75. {ksxt-1.0.4.dist-info → ksxt-1.0.6.dist-info}/top_level.txt +0 -0
ksxt/models/balance.py CHANGED
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel
1
+ from pydantic import BaseModel, model_validator
2
2
  from typing import List
3
3
 
4
4
  from ksxt.models.common import GeneralResponse
@@ -7,14 +7,27 @@ from ksxt.models.common import GeneralResponse
7
7
  class BalanceData(BaseModel):
8
8
  symbol: str
9
9
  name: str
10
- evaluation_price: float
10
+
11
11
  price: float
12
- pnl_amount: float
13
- pnl_ratio: float
12
+ evaluation_price: float
13
+
14
14
  qty: float
15
15
  free_qty: float
16
16
  used_qty: float
17
17
 
18
+ amount: float | None = 0.0
19
+ evaluation_amount: float | None = 0.0
20
+ pnl: float | None = 0.0
21
+ pnl_ratio: float | None = 0.0
22
+
23
+ @model_validator(mode="after")
24
+ def calculate_fields(cls, values):
25
+ values.amount = values.price * values.qty
26
+ values.evaluation_amount = values.evaluation_price * values.qty
27
+ values.pnl = values.evaluation_price - values.price
28
+ values.pnl_ratio = values.pnl / values.price if values.price != 0 else 0.0
29
+ return values
30
+
18
31
 
19
32
  class BalanceInfo(BaseModel):
20
33
  currency: str
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from typing import List, Optional
2
+ from typing import List
3
3
  from pydantic import BaseModel
4
4
 
5
5
  from ksxt.models.common import GeneralResponse
@@ -8,43 +8,37 @@ from ksxt.models.common import GeneralResponse
8
8
  class TransactionInfo(BaseModel):
9
9
  # 주문 고유 아이디
10
10
  uuid: str
11
- # 주문 종류 (ask, bid, deposit, withdrawal)
12
- type: str
13
-
14
- # 금액
15
- amount: float
16
- # 수수료
17
- fee: Optional[float] = 0
18
-
19
11
  # 계좌 번호
20
- account_id: Optional[str] = None
21
- # 화폐 통화 정보
22
- currency: Optional[str] = None
12
+ account_id: str | None = None
23
13
 
24
- # 주문 생성 시간
25
- created_at: datetime
14
+ # 주문 종류 (ask, bid, deposit, withdrawal)
15
+ transaction_type: str
26
16
 
17
+ # order type (limit, market, default)
18
+ order_type: str | None = None
19
+
20
+ # position (long, short)
21
+ tr_position: str | None = None
27
22
 
28
- class OpenedOrderInfo(TransactionInfo):
29
23
  # 종목 정보
30
24
  symbol: str
31
25
  # 가격
32
26
  price: float
33
27
  # 수량
34
28
  qty: float
35
- # 주문 방식
36
- order_type: Optional[str]
29
+ # 금액
30
+ amount: float
37
31
 
32
+ # 세금
33
+ tax: float | None = 0
34
+ # 수수료
35
+ fee: float | None = 0
38
36
 
39
- class ClosedOrderInfo(TransactionInfo):
40
- # 종목 정보
41
- symbol: str
42
- # 가격
43
- price: float
44
- # 수량
45
- qty: float
46
- # 주문 방식
47
- order_type: Optional[str]
37
+ # 화폐 통화 정보
38
+ currency: str | None = None
39
+
40
+ # 주문 생성 시간
41
+ created_at: datetime
48
42
 
49
43
 
50
44
  class WithdrawalHistory(BaseModel):
@@ -56,11 +50,11 @@ class DepositHistory(BaseModel):
56
50
 
57
51
 
58
52
  class OpenedOrderHistory(BaseModel):
59
- history: List[OpenedOrderInfo]
53
+ history: List[TransactionInfo]
60
54
 
61
55
 
62
56
  class ClosedOrderHistory(BaseModel):
63
- history: List[ClosedOrderInfo]
57
+ history: List[TransactionInfo]
64
58
 
65
59
 
66
60
  class KsxtWithdrawalHistoryResponse(GeneralResponse[WithdrawalHistory]):
ksxt/parser/bithumb.py CHANGED
@@ -1,13 +1,10 @@
1
- from datetime import datetime
2
- from operator import attrgetter
1
+ from datetime import datetime, timezone
3
2
  from typing import Dict, List, Optional
4
3
 
5
4
  from ksxt.models.transaction import (
6
5
  ClosedOrderHistory,
7
- ClosedOrderInfo,
8
6
  DepositHistory,
9
7
  OpenedOrderHistory,
10
- OpenedOrderInfo,
11
8
  TransactionInfo,
12
9
  WithdrawalHistory,
13
10
  )
@@ -24,6 +21,10 @@ from ksxt.utils import safer, sorter
24
21
 
25
22
  class BithumbParser(BaseParser):
26
23
  def safe_symbol(self, base_market: str, security: str) -> str:
24
+ # If security already contains a hyphen, assume it's correctly formatted
25
+ if "-" in security:
26
+ return security
27
+
27
28
  return f"{base_market}-{security}"
28
29
 
29
30
  def parse_markets(self, response: List[Dict], base_market: str = "KRW") -> MarketInfo:
@@ -46,8 +47,22 @@ class BithumbParser(BaseParser):
46
47
  warning_code=False if safer.safe_string(market, "market_warning") == "NONE" else True,
47
48
  )
48
49
 
49
- def parse_historical_data(self, response: dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
50
+ def parse_historical_data(
51
+ self,
52
+ response: List[Dict],
53
+ symbol: str,
54
+ start: datetime | None = None,
55
+ end: datetime | None = None,
56
+ base_market: str = "KRW",
57
+ ) -> HistoricalDataInfo:
50
58
  ohlcv = [self._parse_ohlcva(_, base_market) for _ in response]
59
+
60
+ # Filter by start and end datetime
61
+ if start:
62
+ ohlcv = [data for data in ohlcv if data.datetime >= start]
63
+ if end:
64
+ ohlcv = [data for data in ohlcv if data.datetime <= end]
65
+
51
66
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
52
67
 
53
68
  security_name = symbol
@@ -161,7 +176,13 @@ class BithumbParser(BaseParser):
161
176
  return ask_data, bid_data
162
177
 
163
178
  def parse_balance(
164
- self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
179
+ self,
180
+ response: List[Dict],
181
+ base_market: str = "KRW",
182
+ excluded_symbols: Optional[list[str]] = None,
183
+ included_symbols: Optional[list[str]] = None,
184
+ filter_delisted: bool = True,
185
+ min_amount: float = 0,
165
186
  ) -> BalanceInfo:
166
187
  # 1. Filter out entries where 'currency' is not the base_market
167
188
  # 2. Ensure 'unit_currency' is the base_market
@@ -172,11 +193,18 @@ class BithumbParser(BaseParser):
172
193
  if data["currency"] != base_market
173
194
  and data["unit_currency"] == base_market
174
195
  and (excluded_symbols is None or data["currency"] not in excluded_symbols)
196
+ and (included_symbols is None or self.safe_symbol(base_market, data["currency"]) in included_symbols)
175
197
  ]
176
198
 
177
199
  # Initialize balance list
178
200
  balances = [self._parse_balance(data, base_market) for data in filtered_data]
179
201
 
202
+ if filter_delisted:
203
+ balances = [balance for balance in balances if balance.price > 0]
204
+
205
+ if min_amount > 0:
206
+ balances = [balance for balance in balances if balance.amount > min_amount]
207
+
180
208
  # 총 매입금액
181
209
  total_amount = sum(bal.price * bal.qty for bal in balances)
182
210
  total_evaluation_amount = sum(bal.evaluation_price * bal.qty for bal in balances)
@@ -198,8 +226,6 @@ class BithumbParser(BaseParser):
198
226
  name=self.safe_symbol(base_market, safer.safe_string(data, "currency")),
199
227
  evaluation_price=0, # 필요에 따라 적절히 설정
200
228
  price=safer.safe_number(data, "avg_buy_price"),
201
- pnl_amount=0, # 필요에 따라 적절히 설정
202
- pnl_ratio=0, # 필요에 따라 적절히 설정
203
229
  qty=safer.safe_number(data, "balance"),
204
230
  free_qty=safer.safe_number(data, "balance") - safer.safe_number(data, "locked"),
205
231
  used_qty=safer.safe_number(data, "locked"),
@@ -256,38 +282,72 @@ class BithumbParser(BaseParser):
256
282
  market_ask_fee=safer.safe_number(response, "maker_ask_fee"),
257
283
  )
258
284
 
259
- def parse_open_order_history(self, response: dict, base_market: str = "KRW") -> OpenedOrderHistory:
285
+ def parse_open_order_history(
286
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
287
+ ) -> OpenedOrderHistory:
260
288
  orders = [self._parse_open_order_info(order, base_market) for order in response]
289
+
290
+ # Filter by start and end datetime
291
+ if start:
292
+ orders = [
293
+ order for order in orders if order.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
294
+ ]
295
+ if end:
296
+ orders = [
297
+ order for order in orders if order.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
298
+ ]
299
+
261
300
  return OpenedOrderHistory(history=orders)
262
301
 
263
- def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> OpenedOrderInfo:
264
- return OpenedOrderInfo(
302
+ def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> TransactionInfo:
303
+ return TransactionInfo(
265
304
  uuid=safer.safe_string(order, "uuid"),
266
- type=safer.safe_string(order, "side"),
305
+ account_id="",
306
+ transaction_type=safer.safe_string(order, "side"),
307
+ order_type=safer.safe_string(order, "ord_type"),
308
+ tr_position="long",
267
309
  symbol=self.safe_symbol(base_market, safer.safe_string(order, "market")),
268
310
  price=safer.safe_number(order, "price"),
269
311
  qty=safer.safe_number(order, "volume"),
270
312
  amount=safer.safe_number(order, "price") * safer.safe_number(order, "volume"),
313
+ tax=0,
271
314
  fee=safer.safe_number(order, "reserved_fee"),
315
+ currency=base_market,
272
316
  created_at=datetime.fromisoformat(safer.safe_string(order, "created_at")),
273
- order_type=safer.safe_string(order, "ord_type"),
274
317
  )
275
318
 
276
- def parse_closed_order_history(self, response: dict, base_market: str = "KRW") -> ClosedOrderHistory:
319
+ def parse_closed_order_history(
320
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
321
+ ) -> ClosedOrderHistory:
277
322
  orders = [self._parse_closed_order_info(order, base_market) for order in response]
323
+
324
+ # Filter by start and end datetime
325
+ if start:
326
+ orders = [
327
+ order for order in orders if order.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
328
+ ]
329
+ if end:
330
+ orders = [
331
+ order for order in orders if order.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
332
+ ]
333
+
278
334
  return ClosedOrderHistory(history=orders)
279
335
 
280
- def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> ClosedOrderInfo:
281
- return ClosedOrderInfo(
336
+ def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> TransactionInfo:
337
+ return TransactionInfo(
282
338
  uuid=safer.safe_string(order, "uuid"),
283
- type=safer.safe_string(order, "side"),
284
- symbol=safer.safe_string(order, "market"),
339
+ account_id="",
340
+ transaction_type=safer.safe_string(order, "side"),
341
+ order_type=safer.safe_string(order, "ord_type"),
342
+ tr_position="long",
343
+ symbol=self.safe_symbol(base_market, safer.safe_string(order, "market")),
285
344
  price=safer.safe_number(order, "price"),
286
345
  qty=safer.safe_number(order, "volume"),
287
346
  amount=safer.safe_number(order, "price") * safer.safe_number(order, "volume"),
347
+ tax=0,
288
348
  fee=safer.safe_number(order, "reserved_fee"),
349
+ currency=base_market,
289
350
  created_at=datetime.fromisoformat(safer.safe_string(order, "created_at")),
290
- order_type=safer.safe_string(order, "ord_type"),
291
351
  )
292
352
 
293
353
  def parse_cancel_order(self, response: dict, base_market: str = "KRW") -> CancelOrderResponse:
@@ -296,20 +356,61 @@ class BithumbParser(BaseParser):
296
356
  def parse_create_order(self, response: dict, base_market: str = "KRW") -> CreateOrderResponse:
297
357
  return CreateOrderResponse(order_datetime=datetime.now(), order_id=safer.safe_string(response, "uuid"))
298
358
 
299
- def parse_withdrawal_history(self, response: List[Dict], base_market: str = "KRW") -> WithdrawalHistory:
359
+ def parse_withdrawal_history(
360
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
361
+ ) -> WithdrawalHistory:
300
362
  parsed_items = [self._parse_transaction_history_item(item, base_market) for item in response]
363
+
364
+ # Filter by start and end datetime
365
+ if start:
366
+ parsed_items = [
367
+ item
368
+ for item in parsed_items
369
+ if item.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
370
+ ]
371
+ if end:
372
+ parsed_items = [
373
+ item
374
+ for item in parsed_items
375
+ if item.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
376
+ ]
377
+
301
378
  return WithdrawalHistory(history=parsed_items)
302
379
 
303
- def parse_deposit_history(self, response: List[Dict], base_market: str = "KRW") -> DepositHistory:
380
+ def parse_deposit_history(
381
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
382
+ ) -> DepositHistory:
304
383
  parsed_items = [self._parse_transaction_history_item(item, base_market) for item in response]
384
+
385
+ # Filter by start and end datetime
386
+ if start:
387
+ parsed_items = [
388
+ item
389
+ for item in parsed_items
390
+ if item.created_at.astimezone(timezone.utc) >= start.astimezone(timezone.utc)
391
+ ]
392
+ if end:
393
+ parsed_items = [
394
+ item
395
+ for item in parsed_items
396
+ if item.created_at.astimezone(timezone.utc) <= end.astimezone(timezone.utc)
397
+ ]
398
+
305
399
  return DepositHistory(history=parsed_items)
306
400
 
307
401
  def _parse_transaction_history_item(self, item: Dict, base_market: str = "KRW") -> TransactionInfo:
308
402
  return TransactionInfo(
309
403
  uuid=safer.safe_string(item, "uuid"),
310
- type=safer.safe_string(item, "type"),
404
+ account_id="",
405
+ transaction_type=safer.safe_string(item, "type"),
406
+ order_type=safer.safe_string(item, "transaction_type"),
407
+ tr_position="",
408
+ symbol=safer.safe_string(item, "currency"),
409
+ price=1,
410
+ qty=safer.safe_number(item, "amount"),
311
411
  amount=safer.safe_number(item, "amount"),
412
+ tax=0,
312
413
  fee=safer.safe_number(item, "fee"),
313
414
  currency=safer.safe_string(item, "currency"),
314
- created_at=datetime.fromisoformat(safer.safe_string(item, "created_at")),
415
+ created_at=datetime.fromisoformat(safer.safe_string(item, "done_at")),
315
416
  )
@@ -11,10 +11,9 @@ from ksxt.models.ticker import MultiSymbolTickerInfo, TickerInfo
11
11
  from ksxt.models.token import TokenInfo
12
12
  from ksxt.models.transaction import (
13
13
  ClosedOrderHistory,
14
- ClosedOrderInfo,
15
14
  DepositHistory,
16
15
  OpenedOrderHistory,
17
- OpenedOrderInfo,
16
+ TransactionInfo,
18
17
  )
19
18
  from ksxt.parser.parser import BaseParser
20
19
  from ksxt.utils import safer, sorter, timer
@@ -52,9 +51,23 @@ class KoreaInvestParser(BaseParser):
52
51
  warning_code=safer.safe_boolean(market, " market_warning"),
53
52
  )
54
53
 
55
- def parse_historical_data(self, response: Dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
54
+ def parse_historical_data(
55
+ self,
56
+ response: List[Dict],
57
+ symbol: str,
58
+ start: datetime | None = None,
59
+ end: datetime | None = None,
60
+ base_market: str = "KRW",
61
+ ) -> HistoricalDataInfo:
56
62
  safe_response = safer.safe_value(response, "output2")
57
63
  ohlcv = [self._parse_ohlcva(_, base_market) for _ in safe_response]
64
+
65
+ # Filter by start and end datetime
66
+ if start:
67
+ ohlcv = [data for data in ohlcv if data.datetime >= start]
68
+ if end:
69
+ ohlcv = [data for data in ohlcv if data.datetime <= end]
70
+
58
71
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
59
72
 
60
73
  security_name = symbol
@@ -77,9 +90,23 @@ class KoreaInvestParser(BaseParser):
77
90
  acml_amount=safer.safe_number(ohlcva, "acml_tr_pbmn"),
78
91
  )
79
92
 
80
- def parse_historical_index_data(self, response: Dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
93
+ def parse_historical_index_data(
94
+ self,
95
+ response: Dict,
96
+ symbol: str,
97
+ start: datetime | None = None,
98
+ end: datetime | None = None,
99
+ base_market: str = "KRW",
100
+ ) -> HistoricalDataInfo:
81
101
  safe_response = safer.safe_value(response, "output2")
82
102
  ohlcv = [self._parse_index_ohlcva(_, base_market) for _ in safe_response]
103
+
104
+ # Filter by start and end datetime
105
+ if start:
106
+ ohlcv = [data for data in ohlcv if data.datetime >= start]
107
+ if end:
108
+ ohlcv = [data for data in ohlcv if data.datetime <= end]
109
+
83
110
  sorted_ohlcv = sorter.sort_by(ohlcv, key="datetime")
84
111
 
85
112
  security_name = symbol
@@ -154,7 +181,13 @@ class KoreaInvestParser(BaseParser):
154
181
  return ask_data, bid_data
155
182
 
156
183
  def parse_balance(
157
- self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
184
+ self,
185
+ response: List[Dict],
186
+ base_market: str = "KRW",
187
+ excluded_symbols: Optional[list[str]] = None,
188
+ included_symbols: list[str] | None = None,
189
+ filter_delisted: bool = True,
190
+ min_amount: float = 0,
158
191
  ) -> BalanceInfo:
159
192
  safe_response = safer.safe_value(response, "output1")
160
193
 
@@ -162,9 +195,16 @@ class KoreaInvestParser(BaseParser):
162
195
  balances = [
163
196
  self._parse_balance(_, base_market)
164
197
  for _ in safe_response
165
- if excluded_symbols is None or safer.safe_string(_, "pdno") not in excluded_symbols
198
+ if (excluded_symbols is None or safer.safe_string(_, "pdno") not in excluded_symbols)
199
+ and (included_symbols is None or safer.safe_string(_, "pdno") in included_symbols)
166
200
  ]
167
201
 
202
+ if filter_delisted:
203
+ balances = [balance for balance in balances if balance.price > 0]
204
+
205
+ if min_amount > 0:
206
+ balances = [balance for balance in balances if balance.amount > min_amount]
207
+
168
208
  # 총 매입금액
169
209
  total_amount = sum(bal.price * bal.qty for bal in balances)
170
210
  total_evaluation_amount = sum(bal.evaluation_price * bal.qty for bal in balances)
@@ -186,7 +226,7 @@ class KoreaInvestParser(BaseParser):
186
226
  name=safer.safe_string(data, "prdt_name"),
187
227
  evaluation_price=safer.safe_number(data, "evlu_amt"),
188
228
  price=safer.safe_number(data, "pchs_avg_pric"),
189
- pnl_amount=safer.safe_number(data, "evlu_pfls_amt"),
229
+ pnl=safer.safe_number(data, "evlu_pfls_amt"),
190
230
  pnl_ratio=safer.safe_number(data, "evlu_pfls_rt"),
191
231
  qty=safer.safe_number(data, "hldg_qty"),
192
232
  free_qty=safer.safe_number(data, "ord_psbl_qty"),
@@ -242,52 +282,75 @@ class KoreaInvestParser(BaseParser):
242
282
  market_ask_fee=0.0140527 + trading_fee,
243
283
  )
244
284
 
245
- def parse_closed_order_history(self, response: Dict, base_market: str = "KRW") -> ClosedOrderHistory:
285
+ def parse_closed_order_history(
286
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
287
+ ) -> ClosedOrderHistory:
246
288
  safe_response = safer.safe_value(response, "output1")
247
- history = [self._parse_closed_order_info(_, base_market) for _ in safe_response]
248
- return ClosedOrderHistory(history=history)
289
+ orders = [self._parse_closed_order_info(_, base_market) for _ in safe_response]
290
+
291
+ # Filter by start and end datetime
292
+ if start:
293
+ orders = [order for order in orders if order.created_at >= start]
294
+ if end:
295
+ orders = [order for order in orders if order.created_at <= end]
296
+
297
+ return ClosedOrderHistory(history=orders)
249
298
 
250
- def _parse_closed_order_info(self, order: Dict, base_market: str = "KRW") -> ClosedOrderInfo:
299
+ def _parse_closed_order_info(self, order: Dict, base_market: str = "KRW") -> TransactionInfo:
251
300
  if safer.safe_string(order, "sll_buy_dvsn_cd") == "01":
252
301
  _type = "ask"
253
302
  else:
254
303
  _type = "bid"
255
304
 
256
- return ClosedOrderInfo(
305
+ return TransactionInfo(
257
306
  uuid=safer.safe_string(order, "odno"),
258
- type=_type,
307
+ account_id="",
308
+ # account_id=f'{safer.safe_string(order, "CANO")}-{safer.safe_string(order, "ACNT_PRDT_CD")}',
309
+ transaction_type=_type,
259
310
  symbol=safer.safe_string(order, "pdno"),
260
311
  price=safer.safe_number(order, "avg_prvs"),
261
312
  qty=safer.safe_number(order, "tot_ccld_qty"),
262
313
  amount=safer.safe_number(order, "tot_ccld_amt"),
314
+ tax=0,
263
315
  # 단일 주문 이력에는 수수료 관련 정보가 없고, 조회한 전체 목록에 대해 수수료 정보만 존재한다.
264
316
  fee=0,
265
- created_at=datetime.fromisoformat(safer.safe_string(order, "ord_dt")),
266
317
  order_type=safer.safe_string(order, "avg_prvs"),
318
+ created_at=datetime.fromisoformat(safer.safe_string(order, "ord_dt")),
267
319
  )
268
320
 
269
- def parse_open_order_history(self, response: Dict, base_market: str = "KRW") -> OpenedOrderHistory:
321
+ def parse_open_order_history(
322
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
323
+ ) -> OpenedOrderHistory:
270
324
  safe_response = safer.safe_value(response, "output1")
271
- history = [self._parse_open_order_info(_, base_market) for _ in safe_response]
272
- return OpenedOrderHistory(history=history)
325
+ orders = [self._parse_open_order_info(_, base_market) for _ in safe_response]
326
+
327
+ # Filter by start and end datetime
328
+ if start:
329
+ orders = [order for order in orders if order.created_at >= start]
330
+ if end:
331
+ orders = [order for order in orders if order.created_at <= end]
332
+
333
+ return OpenedOrderHistory(history=orders)
273
334
 
274
- def _parse_open_order_info(self, order: Dict, base_market: str = "KRW") -> OpenedOrderInfo:
335
+ def _parse_open_order_info(self, order: Dict, base_market: str = "KRW") -> TransactionInfo:
275
336
  if safer.safe_string(order, "sll_buy_dvsn_cd") == "01":
276
337
  _type = "ask"
277
338
  else:
278
339
  _type = "bid"
279
340
 
280
- return OpenedOrderInfo(
341
+ return TransactionInfo(
281
342
  uuid=safer.safe_string(order, "odno"),
282
- type=_type,
343
+ transaction_type=_type,
283
344
  symbol=safer.safe_string(order, "pdno"),
284
345
  price=safer.safe_number(order, "avg_prvs"),
285
346
  qty=safer.safe_number(order, "tot_ccld_qty"),
286
347
  amount=safer.safe_number(order, "tot_ccld_amt"),
348
+ tax=0,
287
349
  # 단일 주문 이력에는 수수료 관련 정보가 없고, 조회한 전체 목록에 대해 수수료 정보만 존재한다.
288
350
  fee=0,
289
- created_at=datetime.fromisoformat(safer.safe_string(order, "ord_dt")),
351
+ currency=base_market,
290
352
  order_type=safer.safe_string(order, "avg_prvs"),
353
+ created_at=datetime.fromisoformat(safer.safe_string(order, "ord_dt")),
291
354
  )
292
355
 
293
356
  def parse_create_order(self, response: Dict, base_market: str = "KRW") -> CreateOrderResponse:
ksxt/parser/parser.py CHANGED
@@ -1,3 +1,4 @@
1
+ from datetime import datetime
1
2
  from typing import Dict, List, Optional
2
3
  import inspect
3
4
 
@@ -10,10 +11,8 @@ from ksxt.models.orderbook import MultiSymbolOrderBookInfos, OrderBookData, Orde
10
11
  from ksxt.models.ticker import MultiSymbolTickerInfo, TickerInfo
11
12
  from ksxt.models.transaction import (
12
13
  ClosedOrderHistory,
13
- ClosedOrderInfo,
14
14
  DepositHistory,
15
15
  OpenedOrderHistory,
16
- OpenedOrderInfo,
17
16
  TransactionInfo,
18
17
  WithdrawalHistory,
19
18
  )
@@ -30,7 +29,14 @@ class BaseParser:
30
29
  def _parse_market(self, market: Dict) -> SecurityData:
31
30
  self._raise_not_implemented_error()
32
31
 
33
- def parse_historical_data(self, response: dict, symbol: str, base_market: str = "KRW") -> HistoricalDataInfo:
32
+ def parse_historical_data(
33
+ self,
34
+ response: List[Dict],
35
+ symbol: str,
36
+ start: datetime | None = None,
37
+ end: datetime | None = None,
38
+ base_market: str = "KRW",
39
+ ) -> HistoricalDataInfo:
34
40
  self._raise_not_implemented_error()
35
41
 
36
42
  def _parse_ohlcva(self, ohlcva: dict, base_market: str = "KRW") -> OHLCVData:
@@ -63,7 +69,13 @@ class BaseParser:
63
69
  self._raise_not_implemented_error()
64
70
 
65
71
  def parse_balance(
66
- self, response: List[Dict], base_market: str = "KRW", excluded_symbols: Optional[list[str]] = None
72
+ self,
73
+ response: List[Dict],
74
+ base_market: str = "KRW",
75
+ excluded_symbols: Optional[list[str]] = None,
76
+ included_symbols: list[str] | None = None,
77
+ filter_delisted: bool = True,
78
+ min_amount: float = 0,
67
79
  ) -> BalanceInfo:
68
80
  self._raise_not_implemented_error()
69
81
 
@@ -79,16 +91,20 @@ class BaseParser:
79
91
  def parse_trade_fee(self, response: dict, base_market: str = "KRW") -> TradeFeeInfo:
80
92
  self._raise_not_implemented_error()
81
93
 
82
- def parse_open_order_history(self, response: dict, base_market: str = "KRW") -> OpenedOrderHistory:
94
+ def parse_open_order_history(
95
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
96
+ ) -> OpenedOrderHistory:
83
97
  self._raise_not_implemented_error()
84
98
 
85
- def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> OpenedOrderInfo:
99
+ def _parse_open_order_info(self, order: dict, base_market: str = "KRW") -> TransactionInfo:
86
100
  self._raise_not_implemented_error()
87
101
 
88
- def parse_closed_order_history(self, response: dict, base_market: str = "KRW") -> ClosedOrderHistory:
102
+ def parse_closed_order_history(
103
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
104
+ ) -> ClosedOrderHistory:
89
105
  self._raise_not_implemented_error()
90
106
 
91
- def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> ClosedOrderInfo:
107
+ def _parse_closed_order_info(self, order: dict, base_market: str = "KRW") -> TransactionInfo:
92
108
  self._raise_not_implemented_error()
93
109
 
94
110
  def parse_modify_order(self, response: dict, base_market: str = "KRW") -> ModifyOrderResponse:
@@ -100,10 +116,14 @@ class BaseParser:
100
116
  def parse_create_order(self, response: dict, base_market: str = "KRW") -> CreateOrderResponse:
101
117
  self._raise_not_implemented_error()
102
118
 
103
- def parse_withdrawal_history(self, response: List[Dict], base_market: str = "KRW") -> WithdrawalHistory:
119
+ def parse_withdrawal_history(
120
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
121
+ ) -> WithdrawalHistory:
104
122
  self._raise_not_implemented_error()
105
123
 
106
- def parse_deposit_history(self, response: List[Dict], base_market: str = "KRW") -> DepositHistory:
124
+ def parse_deposit_history(
125
+ self, response: List[Dict], start: datetime | None = None, end: datetime | None = None, base_market: str = "KRW"
126
+ ) -> DepositHistory:
107
127
  self._raise_not_implemented_error()
108
128
 
109
129
  def _parse_transaction_history_item(self, item: Dict, base_market: str = "KRW") -> TransactionInfo: