tradingapi 0.3.5__tar.gz → 0.3.6__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 (32) hide show
  1. {tradingapi-0.3.5 → tradingapi-0.3.6}/PKG-INFO +1 -1
  2. {tradingapi-0.3.5 → tradingapi-0.3.6}/pyproject.toml +1 -1
  3. tradingapi-0.3.6/tests/test_calculate_delta_realtime_quotes.py +29 -0
  4. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/allocation.py +1 -1
  5. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/fivepaisa.py +5 -6
  6. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/utils.py +97 -27
  7. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi.egg-info/PKG-INFO +1 -1
  8. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi.egg-info/SOURCES.txt +1 -0
  9. {tradingapi-0.3.5 → tradingapi-0.3.6}/README.md +0 -0
  10. {tradingapi-0.3.5 → tradingapi-0.3.6}/setup.cfg +0 -0
  11. {tradingapi-0.3.5 → tradingapi-0.3.6}/tests/test_find_option_with_delta.py +0 -0
  12. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/__init__.py +0 -0
  13. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/attribution.py +0 -0
  14. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/broker_base.py +0 -0
  15. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/config/commissions_20241216.yaml +0 -0
  16. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/config/config_sample.yaml +0 -0
  17. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/config.py +0 -0
  18. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/dhan.py +0 -0
  19. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/error_handling.py +0 -0
  20. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/exceptions.py +0 -0
  21. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/flattrade.py +0 -0
  22. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/globals.py +0 -0
  23. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/icicidirect.py +0 -0
  24. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/icicidirect_generate_session.py +0 -0
  25. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/market_data_exchanges.py +0 -0
  26. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/proxy_utils.py +0 -0
  27. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/shoonya.py +0 -0
  28. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi/span.py +0 -0
  29. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi.egg-info/dependency_links.txt +0 -0
  30. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi.egg-info/entry_points.txt +0 -0
  31. {tradingapi-0.3.5 → tradingapi-0.3.6}/tradingapi.egg-info/requires.txt +0 -0
  32. {tradingapi-0.3.5 → tradingapi-0.3.6}/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.3.5
3
+ Version: 0.3.6
4
4
  Summary: Trade integration with brokers
5
5
  Author-email: Pankaj Sharma <sharma.pankaj.kumar@gmail.com>
6
6
  License-Expression: MIT
@@ -28,7 +28,7 @@ packages = ["tradingapi"]
28
28
 
29
29
  [project]
30
30
  name = "tradingapi"
31
- version = "0.3.5"
31
+ version = "0.3.6"
32
32
  description = "Trade integration with brokers"
33
33
  readme = "README.md"
34
34
  license = "MIT"
@@ -0,0 +1,29 @@
1
+ import math
2
+ import sys
3
+ from types import SimpleNamespace
4
+ import unittest
5
+ from unittest.mock import patch
6
+
7
+ sys.path.insert(0, "/home/psharma/onedrive/code/tradingapi2")
8
+
9
+ from tradingapi import utils
10
+
11
+
12
+ class CalculateDeltaRealtimeQuotesTest(unittest.TestCase):
13
+ def test_calculate_delta_returns_nan_without_live_bid_ask(self):
14
+ ticker = SimpleNamespace(bid=0.0, ask=0.0, prior_close=123.45)
15
+
16
+ with patch.object(utils, "get_price", return_value=ticker):
17
+ delta = utils.calculate_delta(
18
+ brokers=[],
19
+ long_symbol="TCS_OPT_20260630_PUT_2160",
20
+ price_f=2325.1,
21
+ exchange="NFO",
22
+ mds="mds",
23
+ )
24
+
25
+ self.assertTrue(math.isnan(delta))
26
+
27
+
28
+ if __name__ == "__main__":
29
+ unittest.main()
@@ -24,7 +24,7 @@ _TODAY_SYMLINK = _ALLOCATIONS_DIR / "master_allocation_today.yaml"
24
24
  def load_today_allocation_entry(broker_name: str, strategy_name: str) -> dict:
25
25
  """Return the full allocation entry dict for strategy_name on broker_name today.
26
26
 
27
- Example return value: {'pct': 0.275, 'redis_db': 8}
27
+ Example return value: {'pct': 0.225, 'redis_db': 8}
28
28
  SCALPING strategies include a 'redis_db' key; others have only 'pct'.
29
29
 
30
30
  Reads master_allocation_today.yaml (a symlink to today's dated file).
@@ -3738,12 +3738,11 @@ class FivePaisa(BrokerBase):
3738
3738
  raise BrokerConnectionError(
3739
3739
  "FivePaisa stream session helpers not initialized; connect() must succeed before streaming"
3740
3740
  )
3741
- if not restore_fn(token_path):
3742
- trading_logger.log_warning(
3743
- "Token restore failed during reconnect, attempting fresh login",
3744
- {"broker": self.broker.name},
3745
- )
3746
- fresh_fn(token_path)
3741
+ # Always do a full TOTP re-login for stream reconnects.
3742
+ # restore_fn succeeds (REST verifies OK) but FivePaisa's WS server silently
3743
+ # rejects streaming on a token-restored session — subscriptions are accepted
3744
+ # but no ticks are ever delivered. fresh_fn gets a genuine new session.
3745
+ fresh_fn(token_path)
3747
3746
  req_list_full = expand_symbols_to_request(self.subscribed_symbols)
3748
3747
  if not req_list_full:
3749
3748
  context = create_error_context(operation=operation, symbols=symbols, exchange=exchange)
@@ -2000,10 +2000,14 @@ def update_order_status(
2000
2000
  for attr in required_attributes:
2001
2001
  if not hasattr(fills, attr) or getattr(fills, attr) in [None, "0"]:
2002
2002
  attr_value = getattr(fills, attr, "<missing>")
2003
- trading_logger.log_error(
2003
+ msg = (
2004
2004
  f"Missing or invalid attribute {attr} in order information for broker_order_id: {broker_order_id}. "
2005
2005
  f"value={attr_value!r}, value_type={type(attr_value).__name__}"
2006
2006
  )
2007
+ if attr == "exchange_order_id":
2008
+ trading_logger.log_info(msg)
2009
+ else:
2010
+ trading_logger.log_error(msg)
2007
2011
  return fills
2008
2012
 
2009
2013
  if broker.broker != fills.broker:
@@ -3175,8 +3179,10 @@ def calculate_delta(
3175
3179
  ):
3176
3180
  delta = float("nan")
3177
3181
  if as_of is None:
3178
- ticker = get_price(brokers, long_symbol, checks=["bid", "ask", "prior_close"], exchange=exchange, mds=mds)
3179
- price = (ticker.bid + ticker.ask) / 2 if ticker.bid > 0 and ticker.ask > 0 else ticker.prior_close
3182
+ ticker = get_price(brokers, long_symbol, checks=["bid", "ask"], exchange=exchange, mds=mds)
3183
+ if not (ticker.bid > 0 and ticker.ask > 0):
3184
+ return float("nan")
3185
+ price = (ticker.bid + ticker.ask) / 2
3180
3186
  t = (
3181
3187
  calc_fractional_business_days(
3182
3188
  get_tradingapi_now(),
@@ -3284,30 +3290,14 @@ def find_option_with_delta(
3284
3290
  )
3285
3291
  return -1
3286
3292
 
3287
- # --- Change 1: robust direction seeding via spread-out probes ---
3288
- seed_fractions = [0.1, 0.25, 0.5, 0.75, 0.9]
3289
- seed_indices = sorted({min(n - 1, max(0, int(round(f * (n - 1))))) for f in seed_fractions})
3290
- seed_samples: list[tuple[int, float]] = [] # (idx, |delta|)
3291
- for idx in seed_indices:
3292
- d = _delta_at(idx)
3293
- if not math.isnan(d):
3294
- ad = abs(d)
3295
- seed_samples.append((idx, ad))
3296
- _consider(idx, ad)
3297
-
3298
- if len(seed_samples) < 2:
3299
- if best_index < 0:
3300
- trading_logger.log_warning(
3301
- f"find_option_with_delta: no valid option strike found (seed failure). "
3302
- f"price_f={price_f} target_delta={target_delta} return_lower_delta={return_lower_delta} "
3303
- f"chain_len={n} strike_range=[{strike_lo}, {strike_hi}] "
3304
- f"valid_delta_count={valid_delta_count} nan_delta_count={nan_delta_count} "
3305
- f"total_delta_checks={total_delta_checks} nan_delta_strikes={sorted(set(nan_delta_strikes))}"
3306
- )
3307
- return best_index
3293
+ # Monotonicity is deterministic from option type: calls lose delta as strike rises, puts gain it.
3294
+ increasing = "_PUT_" in option_chain[0]
3308
3295
 
3309
- # Determine monotonicity from lowest- vs. highest-strike valid seeds.
3310
- increasing = seed_samples[-1][1] > seed_samples[0][1]
3296
+ # Seed at midpoint (near ATM) most liquid, least likely to have stale data.
3297
+ mid_seed = n // 2
3298
+ d = _delta_at(mid_seed)
3299
+ if not math.isnan(d):
3300
+ _consider(mid_seed, abs(d))
3311
3301
 
3312
3302
  # --- Change 2: NaN at mid -> scan to nearest valid neighbor instead of shrinking range ---
3313
3303
  NAN_SCAN_CAP = 5
@@ -3382,6 +3372,7 @@ def get_delta_strike(
3382
3372
  exchange="NSE",
3383
3373
  mds: Optional[str] = None,
3384
3374
  as_of: Optional[Union[dt.datetime, dt.date, str, pd.Timestamp]] = None,
3375
+ max_moneyness: float = 0.07,
3385
3376
  ) -> Union[str, None]:
3386
3377
  """Get option strike price for a given delta with enhanced error handling.
3387
3378
 
@@ -3403,6 +3394,10 @@ def get_delta_strike(
3403
3394
  as_of: Optional. When omitted (default), behavior matches pre-change realtime paths.
3404
3395
  When set, underlying and option marks use historical OHLC at that time; T for IV/delta
3405
3396
  uses ``as_of`` instead of now.
3397
+ max_moneyness: Maximum allowed deviation of the found strike from the underlying price,
3398
+ as a fraction (e.g. 0.07 = 7%). Applies symmetrically to ITM and OTM strikes.
3399
+ Rejects results where stale market data produced a plausible-looking but wrong delta.
3400
+ Defaults to 0.07.
3406
3401
 
3407
3402
  Returns:
3408
3403
  Union[str, None]: Option symbol or None if not found
@@ -3473,7 +3468,17 @@ def get_delta_strike(
3473
3468
  as_of=as_of,
3474
3469
  )
3475
3470
  if index >= 0:
3476
- return option_chain[index]
3471
+ sym = option_chain[index]
3472
+ strike = float(sym.split("_")[4])
3473
+ deviation = abs(strike / price_f - 1.0)
3474
+ if deviation > max_moneyness:
3475
+ trading_logger.log_warning(
3476
+ f"get_delta_strike: moneyness sanity check failed. "
3477
+ f"strike={strike} price_f={price_f} deviation={deviation:.1%} max_moneyness={max_moneyness:.1%} "
3478
+ f"delta={delta} option_type={option_type} symbol={sym}"
3479
+ )
3480
+ return None
3481
+ return sym
3477
3482
  else:
3478
3483
  trading_logger.log_debug(f"get_delta_strike: no option found (index={index})")
3479
3484
  return None
@@ -4597,3 +4602,68 @@ def register_strategy_capital(
4597
4602
  except Exception as e:
4598
4603
  trading_logger.log_error(f"Failed to register strategy capital: {e}", exc_info=True)
4599
4604
  return False
4605
+
4606
+
4607
+ def close_all_positions(
4608
+ broker: BrokerBase,
4609
+ strategy: str,
4610
+ exchange: str,
4611
+ paper: bool,
4612
+ refresh_status: bool = True,
4613
+ price_types: list[str] = ["LMT"],
4614
+ exclude_today_expiry: bool = False,
4615
+ publish: bool = True,
4616
+ additional_infos: list[str] | None = None,
4617
+ ) -> list[str]:
4618
+ """Close all open positions for a strategy by placing exit orders.
4619
+
4620
+ Reads open positions from get_pnl_table (authoritative Redis state, not in-memory cache),
4621
+ places one exit order per open position, then publishes updated trades to Redis.
4622
+
4623
+ Args:
4624
+ broker: Broker instance used for order placement and Redis access.
4625
+ strategy: Strategy name.
4626
+ exchange: Exchange string (e.g. "NSE", "BSE") passed to place_combo_order.
4627
+ paper: Paper trading flag passed through to place_combo_order.
4628
+ price_types: Order price type(s), e.g. ["LMT"], ["MKT"], ["BID+0.05"].
4629
+ exclude_today_expiry: If True, skip symbols whose expiry date is today or earlier.
4630
+ publish: Whether to publish trades to Redis after placing orders.
4631
+ additional_infos: Per-order metadata list passed to place_combo_order (e.g. [json.dumps({"message": "..."})]).
4632
+
4633
+ Returns:
4634
+ List of symbols for which exit orders were successfully placed.
4635
+ """
4636
+ closed_symbols: list[str] = []
4637
+ pnl = get_pnl_table(broker, strategy, refresh_status=refresh_status)
4638
+ if pnl.empty:
4639
+ return closed_symbols
4640
+ open_rows = pnl[(pnl["entry_quantity"].fillna(0) + pnl["exit_quantity"].fillna(0)) != 0]
4641
+ for _, row in open_rows.iterrows():
4642
+ symbol = str(row["symbol"])
4643
+ if exclude_today_expiry:
4644
+ parts = symbol.split("_")
4645
+ if len(parts) > 2 and parts[2] <= dt.datetime.today().strftime("%Y%m%d"):
4646
+ trading_logger.log_info(f"close_all_positions: skipping today-expiry symbol {symbol}")
4647
+ continue
4648
+ qty = int(float(row["entry_quantity"]) + float(row["exit_quantity"]))
4649
+ if qty == 0:
4650
+ continue
4651
+ try:
4652
+ place_combo_order(
4653
+ execution_broker=broker,
4654
+ strategy=strategy,
4655
+ symbols=[symbol],
4656
+ quantities=[-qty],
4657
+ entry=False,
4658
+ exchanges=[exchange],
4659
+ price_types=price_types,
4660
+ paper=paper,
4661
+ additional_infos=additional_infos if additional_infos is not None else [""],
4662
+ )
4663
+ trading_logger.log_info(f"close_all_positions: exit placed {symbol} qty={qty}")
4664
+ closed_symbols.append(symbol)
4665
+ except Exception as e:
4666
+ trading_logger.log_error(f"close_all_positions: failed to place exit for {symbol} qty={qty}: {e}")
4667
+ if publish:
4668
+ publish_trades_to_redis(broker, strategy, publish=not paper)
4669
+ return closed_symbols
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tradingapi
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Trade integration with brokers
5
5
  Author-email: Pankaj Sharma <sharma.pankaj.kumar@gmail.com>
6
6
  License-Expression: MIT
@@ -1,5 +1,6 @@
1
1
  README.md
2
2
  pyproject.toml
3
+ tests/test_calculate_delta_realtime_quotes.py
3
4
  tests/test_find_option_with_delta.py
4
5
  tradingapi/__init__.py
5
6
  tradingapi/allocation.py
File without changes
File without changes