tradingapi 0.2.0__tar.gz → 0.2.1__tar.gz

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 (30) hide show
  1. {tradingapi-0.2.0 → tradingapi-0.2.1}/PKG-INFO +14 -1
  2. {tradingapi-0.2.0 → tradingapi-0.2.1}/README.md +13 -0
  3. {tradingapi-0.2.0 → tradingapi-0.2.1}/pyproject.toml +1 -1
  4. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/__init__.py +10 -1
  5. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/broker_base.py +57 -38
  6. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/config/config_sample.yaml +45 -0
  7. tradingapi-0.2.1/tradingapi/dhan.py +2578 -0
  8. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/fivepaisa.py +290 -109
  9. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/flattrade.py +339 -60
  10. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/icicidirect.py +905 -210
  11. tradingapi-0.2.1/tradingapi/market_data_exchanges.py +57 -0
  12. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/proxy_utils.py +9 -10
  13. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/shoonya.py +599 -120
  14. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/utils.py +536 -207
  15. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi.egg-info/PKG-INFO +14 -1
  16. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi.egg-info/SOURCES.txt +1 -1
  17. tradingapi-0.2.0/tests/test_shoonya_symbol_parser.py +0 -0
  18. tradingapi-0.2.0/tradingapi/dhan.py +0 -1686
  19. {tradingapi-0.2.0 → tradingapi-0.2.1}/setup.cfg +0 -0
  20. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/attribution.py +0 -0
  21. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/config/commissions_20241216.yaml +0 -0
  22. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/config.py +0 -0
  23. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/error_handling.py +0 -0
  24. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/exceptions.py +0 -0
  25. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/globals.py +0 -0
  26. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi/icicidirect_generate_session.py +0 -0
  27. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi.egg-info/dependency_links.txt +0 -0
  28. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi.egg-info/entry_points.txt +0 -0
  29. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi.egg-info/requires.txt +0 -0
  30. {tradingapi-0.2.0 → tradingapi-0.2.1}/tradingapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tradingapi
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Trade integration with brokers
5
5
  Author-email: Pankaj Sharma <sharma.pankaj.kumar@gmail.com>
6
6
  License-Expression: MIT
@@ -259,6 +259,19 @@ ICICIDIRECT:
259
259
  USERTOKEN_MAX_AGE_HOURS: 20
260
260
  AUTO_SESSION_TOKEN_CMD: "icicidirect-generate-session --api-key \"${ICICI_API_KEY}\" --user-id \"${ICICI_USER_ID}\" --password \"${ICICI_PASSWORD}\" --totp-token \"${ICICI_TOTP_TOKEN}\"" # Optional non-interactive token command
261
261
  SYMBOLCODES: "/path/to/icicidirect/symbols"
262
+
263
+ # Dhan Broker Configuration
264
+ DHAN:
265
+ CLIENT_ID: "your_client_id"
266
+ ACCESS_TOKEN: "your_access_token" # Optional fallback if TOTP/PIN flow is not used
267
+ TOTP_TOKEN: "your_totp_token" # Optional: used for auto token refresh
268
+ PIN: "your_pin" # Optional: used for auto token refresh
269
+ USERTOKEN: "/path/to/dhan_token.txt" # Optional token cache file
270
+ SYMBOLCODES: "/path/to/dhan/symbols"
271
+ QUOTE_RATE_LIMIT_REDIS_DB: 4 # Optional: dedicated Redis DB for global quote limiter
272
+ QUOTE_RATE_LIMIT_RPS: 1 # Optional: quote API limit in requests/second
273
+ HISTORICAL_RATE_LIMIT_RPS: 10 # Optional: historical API limit in requests/second
274
+ REQUEST_STREAMING_DATA_RATE_LIMIT_RPS: 10 # Optional: streaming subscription request rate in requests/second
262
275
  ```
263
276
 
264
277
  #### ICICIDirect fully automated session-token refresh (no copy/paste)
@@ -240,6 +240,19 @@ ICICIDIRECT:
240
240
  USERTOKEN_MAX_AGE_HOURS: 20
241
241
  AUTO_SESSION_TOKEN_CMD: "icicidirect-generate-session --api-key \"${ICICI_API_KEY}\" --user-id \"${ICICI_USER_ID}\" --password \"${ICICI_PASSWORD}\" --totp-token \"${ICICI_TOTP_TOKEN}\"" # Optional non-interactive token command
242
242
  SYMBOLCODES: "/path/to/icicidirect/symbols"
243
+
244
+ # Dhan Broker Configuration
245
+ DHAN:
246
+ CLIENT_ID: "your_client_id"
247
+ ACCESS_TOKEN: "your_access_token" # Optional fallback if TOTP/PIN flow is not used
248
+ TOTP_TOKEN: "your_totp_token" # Optional: used for auto token refresh
249
+ PIN: "your_pin" # Optional: used for auto token refresh
250
+ USERTOKEN: "/path/to/dhan_token.txt" # Optional token cache file
251
+ SYMBOLCODES: "/path/to/dhan/symbols"
252
+ QUOTE_RATE_LIMIT_REDIS_DB: 4 # Optional: dedicated Redis DB for global quote limiter
253
+ QUOTE_RATE_LIMIT_RPS: 1 # Optional: quote API limit in requests/second
254
+ HISTORICAL_RATE_LIMIT_RPS: 10 # Optional: historical API limit in requests/second
255
+ REQUEST_STREAMING_DATA_RATE_LIMIT_RPS: 10 # Optional: streaming subscription request rate in requests/second
243
256
  ```
244
257
 
245
258
  #### ICICIDirect fully automated session-token refresh (no copy/paste)
@@ -28,7 +28,7 @@ packages = ["tradingapi"]
28
28
 
29
29
  [project]
30
30
  name = "tradingapi"
31
- version = "0.2.0"
31
+ version = "0.2.1"
32
32
  description = "Trade integration with brokers"
33
33
  readme = "README.md"
34
34
  license = "MIT"
@@ -144,6 +144,13 @@ class TradingAPILogger:
144
144
  for handler in handlers:
145
145
  self.logger.addHandler(handler)
146
146
 
147
+ # Child loggers (e.g. tradingapi.utils) propagate here; without this, the same
148
+ # record also reaches root and duplicates when root has its own handlers.
149
+ if handlers:
150
+ self.logger.propagate = False
151
+ else:
152
+ self.logger.propagate = True
153
+
147
154
  self._configured = True
148
155
 
149
156
  # Log configuration
@@ -275,7 +282,9 @@ def configure_logging(
275
282
  backup_count: Number of log files to keep.
276
283
  format_string: Custom format string for log messages.
277
284
  enable_structured_logging: Enable structured logging with additional context.
278
- configure_root_logger: Whether to configure the root logger (can cause duplicate logs if True).
285
+ configure_root_logger: If True, attach the same handlers to the root logger (for third-party
286
+ loggers). The ``tradingapi`` logger sets propagate=False when it has handlers, so package
287
+ messages are not written twice when root is also configured.
279
288
  """
280
289
  trading_logger.configure(
281
290
  level=level,
@@ -1,6 +1,5 @@
1
1
  import datetime as dt
2
2
  import json
3
- import logging
4
3
  import math
5
4
  from abc import ABC, abstractmethod
6
5
  from dataclasses import asdict, dataclass
@@ -11,8 +10,6 @@ import pandas as pd
11
10
  import pytz
12
11
  import redis
13
12
 
14
- logger = logging.getLogger(__name__)
15
-
16
13
  from .exceptions import (
17
14
  TradingAPIError,
18
15
  BrokerConnectionError,
@@ -26,7 +23,10 @@ from .exceptions import (
26
23
  from .error_handling import retry_on_error, safe_execute, log_execution_time, handle_broker_errors, validate_inputs
27
24
  from chameli.dateutils import parse_datetime
28
25
 
29
- # Removed trading_logger import to avoid circular import issues
26
+
27
+ def _get_trading_logger():
28
+ from tradingapi import trading_logger
29
+ return trading_logger
30
30
 
31
31
  NEXT_DAY_TIMESTAMP = int((get_tradingapi_now() + dt.timedelta(days=1)).timestamp())
32
32
 
@@ -291,9 +291,9 @@ class Order:
291
291
  try:
292
292
  # Handle None values
293
293
  if value is None:
294
- logger.warning(
294
+ _get_trading_logger().log_warning(
295
295
  "Received None value, returning 0",
296
- extra={"value": value, "default": 0, "method": "_convert_to_int", "argument_name": argument_name},
296
+ context={"value": value, "default": 0, "method": "_convert_to_int", "argument_name": argument_name},
297
297
  )
298
298
  return 0
299
299
 
@@ -310,9 +310,9 @@ class Order:
310
310
  # Strip whitespace and handle empty strings
311
311
  value = value.strip()
312
312
  if not value:
313
- logger.warning(
313
+ _get_trading_logger().log_warning(
314
314
  "Empty string value, returning 0",
315
- extra={
315
+ context={
316
316
  "value": repr(value),
317
317
  "default": 0,
318
318
  "method": "_convert_to_int",
@@ -325,9 +325,9 @@ class Order:
325
325
  # Try to convert string to float first, then to int
326
326
  return int(float(value))
327
327
  except ValueError:
328
- logger.warning(
328
+ _get_trading_logger().log_warning(
329
329
  "Failed to convert string to int",
330
- extra={
330
+ context={
331
331
  "value": repr(value),
332
332
  "default": 0,
333
333
  "method": "_convert_to_int",
@@ -342,9 +342,9 @@ class Order:
342
342
  # Convert object to string and then to number
343
343
  str_value = str(value).strip()
344
344
  if not str_value:
345
- logger.warning(
345
+ _get_trading_logger().log_warning(
346
346
  "Object converted to empty string, returning 0",
347
- extra={
347
+ context={
348
348
  "value": repr(value),
349
349
  "value_type": type(value).__name__,
350
350
  "default": 0,
@@ -356,9 +356,9 @@ class Order:
356
356
 
357
357
  return int(float(str_value))
358
358
  except (ValueError, TypeError):
359
- logger.warning(
359
+ _get_trading_logger().log_warning(
360
360
  "Failed to convert object to int",
361
- extra={
361
+ context={
362
362
  "value": repr(value),
363
363
  "value_type": type(value).__name__,
364
364
  "default": 0,
@@ -369,10 +369,10 @@ class Order:
369
369
  return 0
370
370
 
371
371
  except Exception as e:
372
- logger.error(
372
+ _get_trading_logger().log_error(
373
373
  "Error converting value to int",
374
- exc_info=True,
375
- extra={
374
+ e,
375
+ context={
376
376
  "value": repr(value),
377
377
  "value_type": type(value).__name__,
378
378
  "method": "_convert_to_int",
@@ -389,9 +389,9 @@ class Order:
389
389
  try:
390
390
  # Handle None values
391
391
  if value is None:
392
- logger.warning(
392
+ _get_trading_logger().log_warning(
393
393
  "Received None value, returning NaN",
394
- extra={
394
+ context={
395
395
  "value": value,
396
396
  "default": float("nan"),
397
397
  "method": "_convert_to_float",
@@ -409,9 +409,9 @@ class Order:
409
409
  # Strip whitespace and handle empty strings
410
410
  value = value.strip()
411
411
  if not value:
412
- logger.warning(
412
+ _get_trading_logger().log_warning(
413
413
  "Empty string value, returning NaN",
414
- extra={
414
+ context={
415
415
  "value": repr(value),
416
416
  "default": float("nan"),
417
417
  "method": "_convert_to_float",
@@ -423,9 +423,9 @@ class Order:
423
423
  try:
424
424
  return float(value)
425
425
  except ValueError:
426
- logger.warning(
426
+ _get_trading_logger().log_warning(
427
427
  "Failed to convert string to float",
428
- extra={
428
+ context={
429
429
  "value": repr(value),
430
430
  "default": float("nan"),
431
431
  "method": "_convert_to_float",
@@ -440,9 +440,9 @@ class Order:
440
440
  # Convert object to string and then to number
441
441
  str_value = str(value).strip()
442
442
  if not str_value:
443
- logger.warning(
443
+ _get_trading_logger().log_warning(
444
444
  "Object converted to empty string, returning NaN",
445
- extra={
445
+ context={
446
446
  "value": repr(value),
447
447
  "value_type": type(value).__name__,
448
448
  "default": float("nan"),
@@ -454,9 +454,9 @@ class Order:
454
454
 
455
455
  return float(str_value)
456
456
  except (ValueError, TypeError):
457
- logger.warning(
457
+ _get_trading_logger().log_warning(
458
458
  "Failed to convert object to float",
459
- extra={
459
+ context={
460
460
  "value": repr(value),
461
461
  "value_type": type(value).__name__,
462
462
  "default": float("nan"),
@@ -467,10 +467,10 @@ class Order:
467
467
  return float("nan")
468
468
 
469
469
  except Exception as e:
470
- logger.error(
470
+ _get_trading_logger().log_error(
471
471
  "Error converting value to float",
472
- exc_info=True,
473
- extra={
472
+ e,
473
+ context={
474
474
  "value": repr(value),
475
475
  "value_type": type(value).__name__,
476
476
  "method": "_convert_to_float",
@@ -493,10 +493,10 @@ class Order:
493
493
  else:
494
494
  return False
495
495
  except Exception as e:
496
- logger.error(
496
+ _get_trading_logger().log_error(
497
497
  "Error converting value to bool",
498
- exc_info=True,
499
- extra={
498
+ e,
499
+ context={
500
500
  "value": value,
501
501
  "value_type": type(value).__name__,
502
502
  "method": "_convert_to_bool",
@@ -721,9 +721,9 @@ class BrokerBase(ABC):
721
721
  try:
722
722
  self._validate_config(kwargs)
723
723
  self._initialize_broker(kwargs)
724
- logger.info(
724
+ _get_trading_logger().log_info(
725
725
  "Broker initialized successfully",
726
- extra={"broker_type": self.__class__.__name__, "config_keys": list(kwargs.keys())},
726
+ context={"broker_type": self.__class__.__name__, "config_keys": list(kwargs.keys())},
727
727
  )
728
728
  except Exception as e:
729
729
  context = create_error_context(
@@ -737,9 +737,9 @@ class BrokerBase(ABC):
737
737
  raise ValidationError("Configuration must be a dictionary")
738
738
 
739
739
  # Add specific validation logic for each broker type
740
- logger.debug(
740
+ _get_trading_logger().log_debug(
741
741
  "Validating broker configuration",
742
- extra={"broker_type": self.__class__.__name__, "config_keys": list(config.keys())},
742
+ context={"broker_type": self.__class__.__name__, "config_keys": list(config.keys())},
743
743
  )
744
744
 
745
745
  def _initialize_broker(self, config: dict):
@@ -834,7 +834,11 @@ class BrokerBase(ABC):
834
834
  Modify an existing order.
835
835
 
836
836
  Args:
837
- **kwargs: Order modification parameters
837
+ **kwargs:
838
+ broker_order_id (str): Broker order ID to modify.
839
+ new_price (float): New limit price (0 for market).
840
+ new_quantity (int): New total quantity.
841
+ order (Order, optional): Order object to bootstrap Redis state if not cached.
838
842
 
839
843
  Returns:
840
844
  Order: Updated order object
@@ -1070,3 +1074,18 @@ class BrokerBase(ABC):
1070
1074
  MarketDataError: If balance retrieval fails
1071
1075
  """
1072
1076
  pass
1077
+
1078
+ def get_margin_requirement(
1079
+ self, combo_symbol: str, order_size: int, exchange: str = "NSE", mds: Optional[str] = None
1080
+ ) -> Optional[float]:
1081
+ """Default broker margin hook. Brokers can override if supported."""
1082
+ _get_trading_logger().log_warning(
1083
+ "get_margin_requirement not implemented for broker",
1084
+ context={
1085
+ "broker_type": self.__class__.__name__,
1086
+ "combo_symbol": combo_symbol,
1087
+ "order_size": order_size,
1088
+ "exchange": exchange,
1089
+ },
1090
+ )
1091
+ return None
@@ -20,6 +20,7 @@ commissions:
20
20
  file: "commissions_20241216.yaml"
21
21
 
22
22
  FIVEPAISA:
23
+ EXCHANGES: [NSE, BSE, MCX]
23
24
  USE_PROXY: false
24
25
  APP_NAME:
25
26
  APP_SOURCE:
@@ -33,7 +34,12 @@ FIVEPAISA:
33
34
  SYMBOLCODES:
34
35
  USERTOKEN:
35
36
 
37
+ # Additional FivePaisa accounts (optional). Pass as account= to FivePaisa().
38
+ # FIVEPAISA_ACCOUNT2:
39
+ # ... (same keys as FIVEPAISA above)
40
+
36
41
  SHOONYA:
42
+ EXCHANGES: [NSE, BSE, MCX]
37
43
  USE_PROXY: false
38
44
  USER:
39
45
  PWD:
@@ -43,7 +49,12 @@ SHOONYA:
43
49
  SYMBOLCODES: "/home/psharma/onedrive/rfiles/data/static/shoonya_symbols"
44
50
  USERTOKEN:
45
51
 
52
+ # Additional Shoonya accounts (optional). Pass as account= to Shoonya().
53
+ # SHOONYA_ACCOUNT2:
54
+ # ... (same keys as SHOONYA above)
55
+
46
56
  ICICIDIRECT:
57
+ EXCHANGES: [NSE, BSE, MCX]
47
58
  USE_PROXY: false
48
59
  API_KEY:
49
60
  API_SECRET:
@@ -72,3 +83,37 @@ ICICIDIRECT:
72
83
  # --totp-token "${ICICI_TOTP_TOKEN}"
73
84
  SYMBOL_MASTER_URL: # Optional: symbol master zip url
74
85
  SYMBOLCODES:
86
+
87
+ # Additional ICICIDirect accounts (optional). Pass as account= to IciciDirect().
88
+ # ICICIDIRECT_ACCOUNT2:
89
+ # ... (same keys as ICICIDIRECT above)
90
+
91
+ DHAN:
92
+ EXCHANGES: [NSE, BSE]
93
+ USE_PROXY: false
94
+ CLIENT_ID:
95
+ ACCESS_TOKEN: # Optional fallback if TOTP/PIN flow is not used
96
+ TOTP_TOKEN: # Optional: used for auto token refresh
97
+ PIN: # Optional: used for auto token refresh
98
+ USERTOKEN: # Optional: token cache file path
99
+ SYMBOLCODES:
100
+ QUOTE_RATE_LIMIT_REDIS_DB: 4 # Optional: dedicated Redis DB for global quote limiter
101
+ QUOTE_RATE_LIMIT_RPS: 1 # Optional: quote API limit in requests/second
102
+ HISTORICAL_RATE_LIMIT_RPS: 10 # Optional: historical API limit in requests/second
103
+ REQUEST_STREAMING_DATA_RATE_LIMIT_RPS: 10 # Optional: streaming subscription request rate in requests/second
104
+
105
+ # Additional DHAN accounts (optional). Name can be anything; pass as account= to Dhan().
106
+ # Example: dh2 = Dhan(account="DHAN_ACCOUNT2"); dh2.connect(redis_db=5)
107
+ DHAN_ACCOUNT2:
108
+ EXCHANGES: [NSE, BSE]
109
+ USE_PROXY: false
110
+ CLIENT_ID:
111
+ ACCESS_TOKEN:
112
+ TOTP_TOKEN:
113
+ PIN:
114
+ USERTOKEN:
115
+ SYMBOLCODES:
116
+ QUOTE_RATE_LIMIT_REDIS_DB: 5
117
+ QUOTE_RATE_LIMIT_RPS: 1
118
+ HISTORICAL_RATE_LIMIT_RPS: 10
119
+ REQUEST_STREAMING_DATA_RATE_LIMIT_RPS: 10