tradingapi 0.1.6__tar.gz → 0.2.0__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.1.6/tradingapi.egg-info → tradingapi-0.2.0}/PKG-INFO +40 -2
  2. tradingapi-0.1.6/PKG-INFO → tradingapi-0.2.0/README.md +37 -18
  3. {tradingapi-0.1.6 → tradingapi-0.2.0}/pyproject.toml +10 -4
  4. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/__init__.py +54 -30
  5. tradingapi-0.2.0/tradingapi/attribution.py +1393 -0
  6. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/broker_base.py +71 -23
  7. tradingapi-0.2.0/tradingapi/config/commissions_20241216.yaml +108 -0
  8. tradingapi-0.2.0/tradingapi/config/config_sample.yaml +74 -0
  9. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/config.py +11 -2
  10. tradingapi-0.2.0/tradingapi/dhan.py +1686 -0
  11. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/error_handling.py +18 -7
  12. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/fivepaisa.py +759 -279
  13. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/flattrade.py +491 -294
  14. tradingapi-0.2.0/tradingapi/icicidirect.py +2245 -0
  15. tradingapi-0.2.0/tradingapi/icicidirect_generate_session.py +225 -0
  16. tradingapi-0.2.0/tradingapi/proxy_utils.py +131 -0
  17. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/shoonya.py +737 -397
  18. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/utils.py +1079 -515
  19. tradingapi-0.1.6/README.md → tradingapi-0.2.0/tradingapi.egg-info/PKG-INFO +56 -1
  20. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi.egg-info/SOURCES.txt +8 -2
  21. tradingapi-0.2.0/tradingapi.egg-info/entry_points.txt +2 -0
  22. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi.egg-info/requires.txt +2 -0
  23. tradingapi-0.1.6/tradingapi/icicidirect.py +0 -680
  24. tradingapi-0.1.6/tradingapi/testing.py +0 -143
  25. {tradingapi-0.1.6 → tradingapi-0.2.0}/setup.cfg +0 -0
  26. {tradingapi-0.1.6 → tradingapi-0.2.0}/tests/test_shoonya_symbol_parser.py +0 -0
  27. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/exceptions.py +0 -0
  28. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi/globals.py +0 -0
  29. {tradingapi-0.1.6 → tradingapi-0.2.0}/tradingapi.egg-info/dependency_links.txt +0 -0
  30. {tradingapi-0.1.6 → tradingapi-0.2.0}/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.1.6
3
+ Version: 0.2.0
4
4
  Summary: Trade integration with brokers
5
5
  Author-email: Pankaj Sharma <sharma.pankaj.kumar@gmail.com>
6
6
  License-Expression: MIT
@@ -14,6 +14,8 @@ Requires-Dist: redis==7.1.0
14
14
  Requires-Dist: pyotp==2.9.0
15
15
  Requires-Dist: httpx<0.29.0,>=0.25.0
16
16
  Requires-Dist: bcrypt<4.0.0
17
+ Requires-Dist: pandas-stubs>=2.0.0
18
+ Requires-Dist: types-redis>=4.6.0
17
19
 
18
20
  # tradingapi
19
21
 
@@ -249,10 +251,46 @@ FLATTRADE:
249
251
  ICICIDIRECT:
250
252
  API_KEY: "your_api_key"
251
253
  API_SECRET: "your_api_secret"
252
- API_SESSION_TOKEN: "your_session_token"
254
+ USER_ID: "your_user_id" # Optional: used by auto token script
255
+ PASSWORD: "your_password" # Optional: used by auto token script
256
+ TOTP_TOKEN: "your_totp_seed" # Optional: used by auto token script
257
+ API_SESSION_TOKEN: "your_session_token" # Optional if you provide one directly
258
+ USERTOKEN: "/path/to/icicidirect_token.txt" # Optional cache file
259
+ USERTOKEN_MAX_AGE_HOURS: 20
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
253
261
  SYMBOLCODES: "/path/to/icicidirect/symbols"
254
262
  ```
255
263
 
264
+ #### ICICIDirect fully automated session-token refresh (no copy/paste)
265
+
266
+ When the package is installed, the `icicidirect-generate-session` CLI is available. Use it as your `AUTO_SESSION_TOKEN_CMD`; it automates login, captures the redirect token, and prints only the session token to stdout.
267
+
268
+ ```bash
269
+ export ICICI_API_KEY="your_api_key"
270
+ export ICICI_USER_ID="your_user_id"
271
+ export ICICI_PASSWORD="your_password"
272
+ export ICICI_TOTP_TOKEN="your_totp_seed"
273
+
274
+ icicidirect-generate-session \
275
+ --api-key "$ICICI_API_KEY" \
276
+ --user-id "$ICICI_USER_ID" \
277
+ --password "$ICICI_PASSWORD" \
278
+ --totp-token "$ICICI_TOTP_TOKEN"
279
+ ```
280
+
281
+ Then set:
282
+
283
+ ```yaml
284
+ ICICIDIRECT:
285
+ 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}\""
286
+ USERTOKEN: "/path/to/icicidirect_token.txt"
287
+ ```
288
+
289
+ Notes:
290
+ - `connect()` executes `AUTO_SESSION_TOKEN_CMD` automatically when no direct/cached token is available.
291
+ - When the package is installed (e.g. `pip install .`), `icicidirect-generate-session` is on PATH. From a source checkout you can run `python -m tradingapi.icicidirect_generate_session`.
292
+ - If `AUTO_SESSION_TOKEN_CMD` is empty and `ICICIDIRECT.AUTO_LOGIN: true`, `connect()` auto-builds a command using `API_KEY/USER_ID/PASSWORD/TOTP_TOKEN` plus optional selector/webdriver overrides.
293
+
256
294
  ### Commission Files
257
295
 
258
296
  Commission files define broker-specific commission structures. They are YAML files located in the same directory as your main config file. Example structure:
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tradingapi
3
- Version: 0.1.6
4
- Summary: Trade integration with brokers
5
- Author-email: Pankaj Sharma <sharma.pankaj.kumar@gmail.com>
6
- License-Expression: MIT
7
- Project-URL: homepage, https://bitbucket.org/incurrency/tradingpapi2
8
- Project-URL: repository, https://bitbucket.org/incurrency/tradingapi2
9
- Description-Content-Type: text/markdown
10
- Requires-Dist: chameli
11
- Requires-Dist: NorenRestApi==0.0.32
12
- Requires-Dist: py5paisa==0.7.20
13
- Requires-Dist: redis==7.1.0
14
- Requires-Dist: pyotp==2.9.0
15
- Requires-Dist: httpx<0.29.0,>=0.25.0
16
- Requires-Dist: bcrypt<4.0.0
17
-
18
1
  # tradingapi
19
2
 
20
3
  Python package for broker integration with unified interfaces for multiple Indian brokers. Provides comprehensive trading functionality including order placement, historical data access, symbology handling, margin calculations, and commission management.
@@ -249,10 +232,46 @@ FLATTRADE:
249
232
  ICICIDIRECT:
250
233
  API_KEY: "your_api_key"
251
234
  API_SECRET: "your_api_secret"
252
- API_SESSION_TOKEN: "your_session_token"
235
+ USER_ID: "your_user_id" # Optional: used by auto token script
236
+ PASSWORD: "your_password" # Optional: used by auto token script
237
+ TOTP_TOKEN: "your_totp_seed" # Optional: used by auto token script
238
+ API_SESSION_TOKEN: "your_session_token" # Optional if you provide one directly
239
+ USERTOKEN: "/path/to/icicidirect_token.txt" # Optional cache file
240
+ USERTOKEN_MAX_AGE_HOURS: 20
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
253
242
  SYMBOLCODES: "/path/to/icicidirect/symbols"
254
243
  ```
255
244
 
245
+ #### ICICIDirect fully automated session-token refresh (no copy/paste)
246
+
247
+ When the package is installed, the `icicidirect-generate-session` CLI is available. Use it as your `AUTO_SESSION_TOKEN_CMD`; it automates login, captures the redirect token, and prints only the session token to stdout.
248
+
249
+ ```bash
250
+ export ICICI_API_KEY="your_api_key"
251
+ export ICICI_USER_ID="your_user_id"
252
+ export ICICI_PASSWORD="your_password"
253
+ export ICICI_TOTP_TOKEN="your_totp_seed"
254
+
255
+ icicidirect-generate-session \
256
+ --api-key "$ICICI_API_KEY" \
257
+ --user-id "$ICICI_USER_ID" \
258
+ --password "$ICICI_PASSWORD" \
259
+ --totp-token "$ICICI_TOTP_TOKEN"
260
+ ```
261
+
262
+ Then set:
263
+
264
+ ```yaml
265
+ ICICIDIRECT:
266
+ 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}\""
267
+ USERTOKEN: "/path/to/icicidirect_token.txt"
268
+ ```
269
+
270
+ Notes:
271
+ - `connect()` executes `AUTO_SESSION_TOKEN_CMD` automatically when no direct/cached token is available.
272
+ - When the package is installed (e.g. `pip install .`), `icicidirect-generate-session` is on PATH. From a source checkout you can run `python -m tradingapi.icicidirect_generate_session`.
273
+ - If `AUTO_SESSION_TOKEN_CMD` is empty and `ICICIDIRECT.AUTO_LOGIN: true`, `connect()` auto-builds a command using `API_KEY/USER_ID/PASSWORD/TOTP_TOKEN` plus optional selector/webdriver overrides.
274
+
256
275
  ### Commission Files
257
276
 
258
277
  Commission files define broker-specific commission structures. They are YAML files located in the same directory as your main config file. Example structure:
@@ -12,7 +12,7 @@ module = ["yaml", "pytz","requests"]
12
12
  ignore_missing_imports = true
13
13
 
14
14
  [[tool.mypy.overrides]]
15
- module = ["numpy", "pyreadr", "rpy2", "pandas", "scipy","redis"]
15
+ module = ["numpy", "pyreadr", "rpy2", "scipy"]
16
16
  ignore_missing_imports = true
17
17
 
18
18
  [build-system]
@@ -21,13 +21,14 @@ build-backend = "setuptools.build_meta"
21
21
 
22
22
  [tool.setuptools]
23
23
  include-package-data = true
24
+ packages = ["tradingapi"]
24
25
 
25
26
  [tool.setuptools.package-data]
26
- "tradingapi2" = ["config/config_sample.yaml", "config/commissions_20241216.yaml"]
27
+ "tradingapi" = ["config/config_sample.yaml", "config/commissions_20241216.yaml"]
27
28
 
28
29
  [project]
29
30
  name = "tradingapi"
30
- version = "0.1.6"
31
+ version = "0.2.0"
31
32
  description = "Trade integration with brokers"
32
33
  readme = "README.md"
33
34
  license = "MIT"
@@ -41,9 +42,14 @@ dependencies = [
41
42
  "redis==7.1.0",
42
43
  "pyotp==2.9.0",
43
44
  "httpx>=0.25.0,<0.29.0",
44
- "bcrypt<4.0.0"
45
+ "bcrypt<4.0.0",
46
+ "pandas-stubs>=2.0.0",
47
+ "types-redis>=4.6.0"
45
48
  ]
46
49
 
50
+ [project.scripts]
51
+ icicidirect-generate-session = "tradingapi.icicidirect_generate_session:main"
52
+
47
53
  [project.urls]
48
54
  homepage = "https://bitbucket.org/incurrency/tradingpapi2"
49
55
  repository = "https://bitbucket.org/incurrency/tradingapi2"
@@ -157,7 +157,7 @@ class TradingAPILogger:
157
157
  },
158
158
  )
159
159
 
160
- def get_logger(self, name: str = None) -> logging.Logger:
160
+ def get_logger(self, name: Optional[str] = None) -> logging.Logger:
161
161
  """Get a logger instance with the specified name."""
162
162
  if name:
163
163
  return logging.getLogger(f"tradingapi.{name}")
@@ -167,8 +167,9 @@ class TradingAPILogger:
167
167
  """Get information about the calling function."""
168
168
  try:
169
169
  # Get the caller frame (skip this method and the logging method)
170
- caller_frame = inspect.currentframe().f_back.f_back
171
- if caller_frame:
170
+ current_frame = inspect.currentframe()
171
+ if current_frame and current_frame.f_back and current_frame.f_back.f_back:
172
+ caller_frame = current_frame.f_back.f_back
172
173
  info = inspect.getframeinfo(caller_frame)
173
174
  return {
174
175
  "caller_filename": info.filename,
@@ -179,22 +180,46 @@ class TradingAPILogger:
179
180
  pass
180
181
  return {}
181
182
 
182
- def log_error(self, message: str, error: Exception = None, context: dict = None, exc_info: bool = True):
183
- """Log an error with structured context."""
184
- extra = context or {}
183
+ def _sanitize_extra(self, context: Optional[dict] = None) -> dict:
184
+ """Rename reserved LogRecord keys in extra context to avoid logging failures."""
185
+ extra = dict(context or {})
186
+ reserved_keys = set(logging.makeLogRecord({}).__dict__.keys()) | {
187
+ "message",
188
+ "asctime",
189
+ }
190
+ sanitized = {}
191
+ for key, value in extra.items():
192
+ new_key = f"context_{key}" if key in reserved_keys else key
193
+ sanitized[new_key] = value
194
+ return sanitized
195
+
196
+ def log_error(
197
+ self, message: str, error: Optional[Exception] = None, context: Optional[dict] = None, exc_info: bool = True
198
+ ):
199
+ """Log an error with structured context. Extra values are sanitized to single-line so
200
+ parse_log_errors.py can reliably match the ERROR line (same format as other tradingapi logs).
201
+ """
202
+ extra = self._sanitize_extra(context)
185
203
  if error:
186
204
  extra["error_type"] = type(error).__name__
187
- extra["error_message"] = str(error)
205
+ # Keep error_message single-line so the log line matches parse_log_errors structured format
206
+ err_msg = str(error).replace("\n", " ").replace("\r", " ").strip()
207
+ extra["error_message"] = err_msg[:500] if len(err_msg) > 500 else err_msg
188
208
 
189
209
  # Add caller information
190
210
  caller_info = self._get_caller_info()
191
211
  extra.update(caller_info)
192
212
 
213
+ # Sanitize any other extra values that might contain newlines (e.g. from create_error_context)
214
+ for key, value in list(extra.items()):
215
+ if isinstance(value, str) and ("\n" in value or "\r" in value):
216
+ extra[key] = value.replace("\n", " ").replace("\r", " ").strip()[:500]
217
+
193
218
  self.logger.error(message, extra=extra, exc_info=exc_info)
194
219
 
195
- def log_warning(self, message: str, context: dict = None):
220
+ def log_warning(self, message: str, context: Optional[dict] = None):
196
221
  """Log a warning with structured context."""
197
- extra = context or {}
222
+ extra = self._sanitize_extra(context)
198
223
 
199
224
  # Add caller information
200
225
  caller_info = self._get_caller_info()
@@ -202,9 +227,9 @@ class TradingAPILogger:
202
227
 
203
228
  self.logger.warning(message, extra=extra)
204
229
 
205
- def log_info(self, message: str, context: dict = None):
230
+ def log_info(self, message: str, context: Optional[dict] = None):
206
231
  """Log an info message with structured context."""
207
- extra = context or {}
232
+ extra = self._sanitize_extra(context)
208
233
 
209
234
  # Add caller information
210
235
  caller_info = self._get_caller_info()
@@ -212,9 +237,9 @@ class TradingAPILogger:
212
237
 
213
238
  self.logger.info(message, extra=extra)
214
239
 
215
- def log_debug(self, message: str, context: dict = None):
240
+ def log_debug(self, message: str, context: Optional[dict] = None):
216
241
  """Log a debug message with structured context."""
217
- extra = context or {}
242
+ extra = self._sanitize_extra(context)
218
243
 
219
244
  # Add caller information
220
245
  caller_info = self._get_caller_info()
@@ -331,23 +356,24 @@ def initialize_config(config_file_path: str, force_reload: bool = True):
331
356
  raise
332
357
 
333
358
 
334
- initialize_config(get_default_config_path())
359
+ initialize_config(str(get_default_config_path()))
335
360
 
336
361
 
337
362
  def enable_runtime_log_level_toggle(enable: bool = True):
338
363
  """
339
364
  Enable runtime log level toggling via SIGUSR1 signal.
340
-
365
+
341
366
  When enabled, sending SIGUSR1 to the process will toggle between
342
367
  DEBUG and INFO logging levels.
343
-
368
+
344
369
  This implementation supports handler chaining - if another package
345
370
  has already registered a SIGUSR1 handler, both handlers will execute
346
371
  (this handler runs first, then calls the previous handler).
347
-
372
+
348
373
  Args:
349
374
  enable: If True, register the signal handler. If False, remove it.
350
375
  """
376
+
351
377
  def toggle_debug(signum, frame):
352
378
  current_level = trading_logger.logger.level
353
379
  if current_level == logging.DEBUG:
@@ -357,8 +383,7 @@ def enable_runtime_log_level_toggle(enable: bool = True):
357
383
  for handler in trading_logger.logger.handlers:
358
384
  handler.setLevel(logging.INFO)
359
385
  trading_logger.log_info(
360
- "Log level changed to INFO via signal",
361
- {"signal": "SIGUSR1", "previous_level": "DEBUG"}
386
+ "Log level changed to INFO via signal", {"signal": "SIGUSR1", "previous_level": "DEBUG"}
362
387
  )
363
388
  else:
364
389
  # Change to DEBUG
@@ -368,16 +393,17 @@ def enable_runtime_log_level_toggle(enable: bool = True):
368
393
  handler.setLevel(logging.DEBUG)
369
394
  trading_logger.log_info(
370
395
  "Log level changed to DEBUG via signal",
371
- {"signal": "SIGUSR1", "previous_level": logging.getLevelName(current_level)}
396
+ {"signal": "SIGUSR1", "previous_level": logging.getLevelName(current_level)},
372
397
  )
373
-
398
+
374
399
  if enable:
375
400
  try:
376
401
  # Get the current handler (if any) before registering ours
377
402
  current_handler = signal.signal(signal.SIGUSR1, toggle_debug)
378
-
403
+
379
404
  # If there was a previous handler, chain it
380
405
  if current_handler not in (signal.SIG_DFL, signal.SIG_IGN, None):
406
+
381
407
  def chained_handler(signum, frame):
382
408
  # Execute our handler first
383
409
  toggle_debug(signum, frame)
@@ -388,10 +414,9 @@ def enable_runtime_log_level_toggle(enable: bool = True):
388
414
  except Exception as e:
389
415
  # Log but don't fail if previous handler has issues
390
416
  trading_logger.log_warning(
391
- "Error in chained SIGUSR1 handler",
392
- {"error": str(e), "error_type": type(e).__name__}
417
+ "Error in chained SIGUSR1 handler", {"error": str(e), "error_type": type(e).__name__}
393
418
  )
394
-
419
+
395
420
  # Register the chained handler
396
421
  signal.signal(signal.SIGUSR1, chained_handler)
397
422
  trading_logger.log_info(
@@ -399,19 +424,18 @@ def enable_runtime_log_level_toggle(enable: bool = True):
399
424
  {
400
425
  "signal": "SIGUSR1",
401
426
  "usage": "kill -SIGUSR1 <pid> to toggle DEBUG/INFO",
402
- "previous_handler": str(current_handler)
403
- }
427
+ "previous_handler": str(current_handler),
428
+ },
404
429
  )
405
430
  else:
406
431
  trading_logger.log_info(
407
432
  "Runtime log level toggle enabled",
408
- {"signal": "SIGUSR1", "usage": "kill -SIGUSR1 <pid> to toggle DEBUG/INFO"}
433
+ {"signal": "SIGUSR1", "usage": "kill -SIGUSR1 <pid> to toggle DEBUG/INFO"},
409
434
  )
410
435
  except (ValueError, OSError) as e:
411
436
  # Signal might not be available on all platforms
412
437
  trading_logger.log_warning(
413
- "Could not register signal handler for log level toggle",
414
- {"error": str(e), "platform": sys.platform}
438
+ "Could not register signal handler for log level toggle", {"error": str(e), "platform": sys.platform}
415
439
  )
416
440
  else:
417
441
  # Restore default handler