quantplay 2.1.140__tar.gz → 2.1.142__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 (100) hide show
  1. {quantplay-2.1.140 → quantplay-2.1.142}/PKG-INFO +1 -1
  2. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/motilal.py +22 -47
  3. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/upstox.py +1 -1
  4. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/zerodha.py +28 -14
  5. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay.egg-info/PKG-INFO +1 -1
  6. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay.egg-info/SOURCES.txt +1 -0
  7. {quantplay-2.1.140 → quantplay-2.1.142}/setup.py +1 -1
  8. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_motilal_login.py +22 -0
  9. quantplay-2.1.142/tests/test_zerodha_positions_add_ltp.py +73 -0
  10. {quantplay-2.1.140 → quantplay-2.1.142}/README.md +0 -0
  11. {quantplay-2.1.140 → quantplay-2.1.142}/pyproject.toml +0 -0
  12. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/__init__.py +0 -0
  13. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/__init__.py +0 -0
  14. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/aliceblue.py +0 -0
  15. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/aliceblue_utils/__init__.py +0 -0
  16. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/aliceblue_utils/aliceblue_oauth.py +0 -0
  17. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/aliceblue_v2.py +0 -0
  18. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/angelone.py +0 -0
  19. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/angelone_utils/__init__.py +0 -0
  20. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/angelone_utils/angeloneWS.py +0 -0
  21. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/auto_login/__init__.py +0 -0
  22. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/breeze/__init__.py +0 -0
  23. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/breeze/breeze_utils.py +0 -0
  24. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/broker_factory.py +0 -0
  25. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/dhan.py +0 -0
  26. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/finvasia_utils/__init__.py +0 -0
  27. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
  28. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/finvasia_utils/finvasia_oauth.py +0 -0
  29. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/five_paisa.py +0 -0
  30. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/flattrade.py +0 -0
  31. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/ft_utils/__init__.py +0 -0
  32. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/ft_utils/flattrade_utils.py +0 -0
  33. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/ft_utils/ft_noren.py +0 -0
  34. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/generics/__init__.py +0 -0
  35. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/generics/broker.py +0 -0
  36. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/icici_direct.py +0 -0
  37. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/iifl_xts.py +0 -0
  38. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/jainam_xts.py +0 -0
  39. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/kite_utils.py +0 -0
  40. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/kotak.py +0 -0
  41. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/kotak_utils/__init__.py +0 -0
  42. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/kotak_utils/kotak_ws.py +0 -0
  43. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/kotak_utils/kotak_ws_lib.py +0 -0
  44. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/noren.py +0 -0
  45. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/shoonya.py +0 -0
  46. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/uplink/__init__.py +0 -0
  47. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/uplink/uplink_utils.py +0 -0
  48. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts.py +0 -0
  49. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils/Connect.py +0 -0
  50. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils/Exception.py +0 -0
  51. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
  52. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils/__init__.py +0 -0
  53. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils_v2/ConnectV2.py +0 -0
  54. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils_v2/InteractiveSocketClientV2.py +0 -0
  55. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/broker/xts_utils_v2/__init__.py +0 -0
  56. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/core/__init__.py +0 -0
  57. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/core/strategy.py +0 -0
  58. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/exception/__init__.py +0 -0
  59. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/exception/exceptions.py +0 -0
  60. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/indicator/__init__.py +0 -0
  61. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/indicator/iv.py +0 -0
  62. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/model/__init__.py +0 -0
  63. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/model/broker.py +0 -0
  64. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/model/broker_response.py +0 -0
  65. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/model/generics.py +0 -0
  66. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/model/instrument_data.py +0 -0
  67. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/model/order_event.py +0 -0
  68. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/py.typed +0 -0
  69. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/strategy/__init__.py +0 -0
  70. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/strategy/iv_spike.py +0 -0
  71. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/strategy/obuy.py +0 -0
  72. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/__init__.py +0 -0
  73. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/caching.py +0 -0
  74. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/constant.py +0 -0
  75. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/exchange.py +0 -0
  76. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/number_utils.py +0 -0
  77. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/pickle_utils.py +0 -0
  78. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/redaction.py +0 -0
  79. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/utils/selenium_utils.py +0 -0
  80. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/wrapper/__init__.py +0 -0
  81. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/wrapper/aws/__init__.py +0 -0
  82. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/wrapper/aws/s3.py +0 -0
  83. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay/wrapper/redis.py +0 -0
  84. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay.egg-info/dependency_links.txt +0 -0
  85. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay.egg-info/requires.txt +0 -0
  86. {quantplay-2.1.140 → quantplay-2.1.142}/quantplay.egg-info/top_level.txt +0 -0
  87. {quantplay-2.1.140 → quantplay-2.1.142}/setup.cfg +0 -0
  88. {quantplay-2.1.140 → quantplay-2.1.142}/tests/__init__.py +0 -0
  89. {quantplay-2.1.140 → quantplay-2.1.142}/tests/conftest.py +0 -0
  90. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_aliceblue_unified.py +0 -0
  91. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_deltax_positions.py +0 -0
  92. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_deltax_root_url.py +0 -0
  93. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_jainam_xts.py +0 -0
  94. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_kotak_order_feed_url.py +0 -0
  95. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_log_redaction.py +0 -0
  96. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_sub_client_all_types.py +0 -0
  97. {quantplay-2.1.140 → quantplay-2.1.142}/tests/test_xts_v2_modify_order.py +0 -0
  98. {quantplay-2.1.140 → quantplay-2.1.142}/tests/wrapper/__init__.py +0 -0
  99. {quantplay-2.1.140 → quantplay-2.1.142}/tests/wrapper/aws/__init__.py +0 -0
  100. {quantplay-2.1.140 → quantplay-2.1.142}/tests/wrapper/aws/s3_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantplay
3
- Version: 2.1.140
3
+ Version: 2.1.142
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -85,49 +85,26 @@ class Motilal(Broker):
85
85
 
86
86
  self.instrument_data_by_exchange = {}
87
87
 
88
- uat = ""
89
- if is_uat:
90
- uat = "uat"
91
-
92
- self.url = (
93
- "https://{}openapi.motilaloswal.com/rest/login/v7/authdirectapi".format(uat)
94
- )
95
- self.otp_url = (
96
- "https://{}openapi.motilaloswal.com/rest/login/v5/resendotp".format(uat)
97
- )
98
- self.verify_otp_url = (
99
- "https://{}openapi.motilaloswal.com/rest/login/v5/verifyotp".format(uat)
100
- )
101
- self.ltp_url = (
102
- "https://{}openapi.motilaloswal.com/rest/report/v1/getltpdata".format(uat)
103
- )
104
- self.place_order_url = (
105
- "https://{}openapi.motilaloswal.com/rest/trans/v1/placeorder".format(uat)
106
- )
107
- self.get_profile_url = (
108
- "https://{}openapi.motilaloswal.com/rest/login/v1/getprofile".format(uat)
109
- )
110
- self.margin_summary_url = "https://{}openapi.motilaloswal.com/rest/report/v1/getreportmarginsummary".format(
111
- uat
112
- )
113
- self.modify_order_url = (
114
- "https://{}openapi.motilaloswal.com/rest/trans/v2/modifyorder".format(uat)
115
- )
116
- self.order_book_url = (
117
- "https://{}openapi.motilaloswal.com/rest/book/v1/getorderbook".format(uat)
118
- )
119
- self.cancel_order_url = (
120
- "https://{}openapi.motilaloswal.com/rest/trans/v1/cancelorder".format(uat)
121
- )
122
- self.positions_url = (
123
- "https://{}openapi.motilaloswal.com/rest/book/v1/getposition".format(uat)
124
- )
125
- self.holdings_url = (
126
- "https://{}openapi.motilaloswal.com/rest/report/v1/getdpholding".format(uat)
127
- )
128
- self.order_details_url = "https://{}openapi.motilaloswal.com/rest/book/v2/getorderdetailbyuniqueorderid".format(
129
- uat
130
- )
88
+ # Versions per Motilal's API docs: the v7 login token is rejected (MO8001) by
89
+ # the older v1 data endpoints.
90
+ base = (
91
+ "https://openapi.motilaloswaluat.com/rest"
92
+ if is_uat
93
+ else "https://openapi.motilaloswal.com/rest"
94
+ )
95
+ self.url = f"{base}/login/v7/authdirectapi"
96
+ self.otp_url = f"{base}/login/v5/resendotp"
97
+ self.verify_otp_url = f"{base}/login/v5/verifyotp"
98
+ self.get_profile_url = f"{base}/login/v5/getprofile"
99
+ self.ltp_url = f"{base}/report/v3/getltpdata"
100
+ self.margin_summary_url = f"{base}/report/v3/getreportmarginsummary"
101
+ self.holdings_url = f"{base}/report/v3/getdpholding"
102
+ self.positions_url = f"{base}/book/v4/getposition"
103
+ self.order_book_url = f"{base}/book/v5/getorderbook"
104
+ self.order_details_url = f"{base}/book/v5/getorderdetailbyuniqueorderid"
105
+ self.place_order_url = f"{base}/trans/v2/placeorder"
106
+ self.modify_order_url = f"{base}/trans/v5/modifyorder"
107
+ self.cancel_order_url = f"{base}/trans/v2/cancelorder"
131
108
 
132
109
  try:
133
110
  if headers:
@@ -521,9 +498,7 @@ class Motilal(Broker):
521
498
  retry_on_exception=retry_exception,
522
499
  )
523
500
  def __post_request(self, url: str, data: Any):
524
- if not data:
525
- data = {"Clientcode": self.headers["vendorinfo"]}
526
- data = json.dumps(data)
501
+ data = json.dumps(data or {})
527
502
 
528
503
  api_response = requests.post(
529
504
  url,
@@ -588,7 +563,7 @@ class Motilal(Broker):
588
563
  holdings_df = holdings_df.with_columns(
589
564
  pl.struct(["exchange", "tradingsymbol"])
590
565
  .map_elements(
591
- lambda x: int(self.ltp(x["exchange"], x["tradingsymbol"])),
566
+ lambda x: float(self.ltp(x["exchange"], x["tradingsymbol"])),
592
567
  return_dtype=pl.Float64,
593
568
  )
594
569
  .alias("price")
@@ -360,7 +360,7 @@ class Upstox(Broker):
360
360
  holdings_df = holdings_df.with_columns(
361
361
  pl.struct(["exchange", "tradingsymbol"])
362
362
  .map_elements(
363
- lambda x: int(self.ltp(x["exchange"], x["tradingsymbol"])),
363
+ lambda x: float(self.ltp(x["exchange"], x["tradingsymbol"])),
364
364
  return_dtype=pl.Float64,
365
365
  )
366
366
  .alias("price")
@@ -461,26 +461,40 @@ class Zerodha(Broker):
461
461
  positions_df = positions_df.with_columns(
462
462
  (pl.col("exchange") + ":" + pl.col("tradingsymbol")).alias("exchange_symbol")
463
463
  )
464
- symbols = (
465
- positions_df.select(pl.col("exchange_symbol").unique()).to_series().to_list()
466
- )
467
- symbol_ltps = self.get_ltps(symbols)
468
- ltp_map: dict[str, float] = {}
469
- for exchange_symbol in symbol_ltps:
470
- ltp_map[exchange_symbol] = float(symbol_ltps[exchange_symbol]["last_price"])
464
+ if add_ltp:
465
+ symbols = (
466
+ positions_df.select(pl.col("exchange_symbol").unique())
467
+ .to_series()
468
+ .to_list()
469
+ )
470
+ symbol_ltps = self.get_ltps(symbols)
471
+ ltp_map: dict[str, float] = {}
472
+ for exchange_symbol in symbol_ltps:
473
+ ltp_map[exchange_symbol] = float(
474
+ symbol_ltps[exchange_symbol]["last_price"]
475
+ )
471
476
 
472
- positions_df = positions_df.with_columns(
473
- pl.col("exchange_symbol")
474
- .replace_strict(ltp_map, default=0.0)
475
- .cast(pl.Float64)
476
- .alias("ltp")
477
- )
477
+ positions_df = positions_df.with_columns(
478
+ pl.col("exchange_symbol")
479
+ .replace_strict(ltp_map, default=0.0)
480
+ .cast(pl.Float64)
481
+ .alias("ltp")
482
+ )
483
+ else:
484
+ # kite.ltp() needs a paid market-data subscription; callers that only
485
+ # display positions skip it and fill live prices themselves.
486
+ positions_df = positions_df.with_columns(
487
+ pl.lit(None).cast(pl.Float64).alias("ltp")
488
+ )
478
489
 
479
490
  positions_df = positions_df.with_columns(
480
491
  (pl.col("sell_value") - pl.col("buy_value")).alias("pnl")
481
492
  )
482
493
  positions_df = positions_df.with_columns(
483
- (pl.col("quantity") * pl.col("ltp") + pl.col("pnl")).alias("pnl")
494
+ pl.when(pl.col("quantity") == 0)
495
+ .then(pl.col("pnl"))
496
+ .otherwise(pl.col("quantity") * pl.col("ltp") + pl.col("pnl"))
497
+ .alias("pnl")
484
498
  )
485
499
 
486
500
  positions_df = positions_df.with_columns(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantplay
3
- Version: 2.1.140
3
+ Version: 2.1.142
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -92,6 +92,7 @@ tests/test_log_redaction.py
92
92
  tests/test_motilal_login.py
93
93
  tests/test_sub_client_all_types.py
94
94
  tests/test_xts_v2_modify_order.py
95
+ tests/test_zerodha_positions_add_ltp.py
95
96
  tests/wrapper/__init__.py
96
97
  tests/wrapper/aws/__init__.py
97
98
  tests/wrapper/aws/s3_test.py
@@ -21,7 +21,7 @@ requirements = [
21
21
  setup(
22
22
  name="quantplay",
23
23
  long_description=Path("README.md").read_text(),
24
- version="2.1.140",
24
+ version="2.1.142",
25
25
  install_requires=requirements,
26
26
  tests_require=[],
27
27
  packages=find_packages(),
@@ -56,3 +56,25 @@ def test_accounts_do_not_share_header_state() -> None:
56
56
  def test_login_without_secret_is_rejected() -> None:
57
57
  with pytest.raises(RetryableException, match="Missing Arguments"):
58
58
  _login(api_secret=None)
59
+
60
+
61
+ def test_data_endpoints_use_versions_that_accept_v7_tokens() -> None:
62
+ client, _ = _login()
63
+ assert client.get_profile_url.endswith("/login/v5/getprofile")
64
+ assert client.holdings_url.endswith("/report/v3/getdpholding")
65
+ assert client.positions_url.endswith("/book/v4/getposition")
66
+ assert client.order_book_url.endswith("/book/v5/getorderbook")
67
+ assert client.place_order_url.endswith("/trans/v2/placeorder")
68
+ assert client.modify_order_url.endswith("/trans/v5/modifyorder")
69
+ assert client.cancel_order_url.endswith("/trans/v2/cancelorder")
70
+
71
+
72
+ def test_reads_send_an_empty_body() -> None:
73
+ # v3+ rejects a client sending its own code: MO2031 "Client and Vendor Cannot
74
+ # Enter UserId".
75
+ client, _ = _login()
76
+ response = MagicMock()
77
+ response.json.return_value = {"status": "SUCCESS", "data": []}
78
+ with patch("quantplay.broker.motilal.requests.post", return_value=response) as post:
79
+ client.margins()
80
+ assert post.call_args.kwargs["data"] == "{}"
@@ -0,0 +1,73 @@
1
+ from typing import Any
2
+ from unittest.mock import MagicMock
3
+
4
+ import polars as pl
5
+ import pytest
6
+
7
+ from quantplay.broker.zerodha import Zerodha
8
+ from quantplay.exception.exceptions import TokenException
9
+
10
+
11
+ def _position(symbol: str, quantity: int, buy: float, sell: float) -> dict[str, Any]:
12
+ return {
13
+ "tradingsymbol": symbol,
14
+ "exchange": "NFO",
15
+ "product": "NRML",
16
+ "quantity": quantity,
17
+ "average_price": 100.0,
18
+ "buy_quantity": 50,
19
+ "sell_quantity": 50 - quantity,
20
+ "buy_value": buy,
21
+ "sell_value": sell,
22
+ "buy_price": 100.0,
23
+ "sell_price": 100.0,
24
+ }
25
+
26
+
27
+ @pytest.fixture
28
+ def broker() -> Zerodha:
29
+ zerodha = object.__new__(Zerodha)
30
+ zerodha.wrapper = MagicMock()
31
+ zerodha.invoke_kite_api = MagicMock( # type: ignore[method-assign]
32
+ return_value={
33
+ "net": [
34
+ _position("NIFTY26N0624000CE", 50, 5000.0, 0.0),
35
+ _position("NIFTY26N0624000PE", 0, 5000.0, 6000.0),
36
+ ]
37
+ }
38
+ )
39
+ zerodha.symbol_attribute = MagicMock(return_value=123) # type: ignore[method-assign]
40
+ return zerodha
41
+
42
+
43
+ def test_add_ltp_false_skips_market_data_call(broker: Zerodha) -> None:
44
+ # kite.ltp() fails without a market-data subscription; add_ltp=False must not call it
45
+ broker.get_ltps = MagicMock( # type: ignore[method-assign]
46
+ side_effect=TokenException("LTP Calls Require Market Data Subscription")
47
+ )
48
+
49
+ positions = broker.positions(add_ltp=False)
50
+
51
+ broker.get_ltps.assert_not_called()
52
+ rows = {r["tradingsymbol"]: r for r in positions.to_dicts()}
53
+ assert rows["NIFTY26N0624000CE"]["ltp"] is None
54
+ assert rows["NIFTY26N0624000CE"]["pnl"] is None
55
+ assert rows["NIFTY26N0624000PE"]["ltp"] is None
56
+ assert rows["NIFTY26N0624000PE"]["pnl"] == 1000.0
57
+
58
+
59
+ def test_add_ltp_true_unchanged(broker: Zerodha) -> None:
60
+ broker.get_ltps = MagicMock( # type: ignore[method-assign]
61
+ return_value={
62
+ "NFO:NIFTY26N0624000CE": {"last_price": 120.0},
63
+ "NFO:NIFTY26N0624000PE": {"last_price": 80.0},
64
+ }
65
+ )
66
+
67
+ positions = broker.positions(add_ltp=True)
68
+
69
+ rows = {r["tradingsymbol"]: r for r in positions.to_dicts()}
70
+ assert rows["NIFTY26N0624000CE"]["ltp"] == 120.0
71
+ assert rows["NIFTY26N0624000CE"]["pnl"] == 50 * 120.0 - 5000.0
72
+ assert rows["NIFTY26N0624000PE"]["pnl"] == 1000.0
73
+ assert positions.schema == pl.Schema(broker.positions_schema)
File without changes
File without changes
File without changes