quantplay 2.0.178__tar.gz → 2.0.181__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 (78) hide show
  1. {quantplay-2.0.178 → quantplay-2.0.181}/PKG-INFO +1 -1
  2. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/broker_factory.py +2 -0
  3. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/icici_direct.py +2 -2
  4. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/kotak.py +72 -0
  5. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/noren.py +1 -1
  6. quantplay-2.0.181/quantplay/core/strategy.py +193 -0
  7. quantplay-2.0.181/quantplay/indicator/iv.py +37 -0
  8. quantplay-2.0.181/quantplay/strategy/iv_spike.py +93 -0
  9. quantplay-2.0.181/quantplay/strategy/obuy.py +51 -0
  10. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/wrapper/redis.py +2 -2
  11. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay.egg-info/PKG-INFO +1 -1
  12. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay.egg-info/SOURCES.txt +7 -0
  13. {quantplay-2.0.178 → quantplay-2.0.181}/setup.py +1 -1
  14. quantplay-2.0.181/tests/conftest.py +0 -0
  15. quantplay-2.0.181/tests/wrapper/__init__.py +0 -0
  16. quantplay-2.0.181/tests/wrapper/aws/__init__.py +0 -0
  17. {quantplay-2.0.178 → quantplay-2.0.181}/README.md +0 -0
  18. {quantplay-2.0.178 → quantplay-2.0.181}/pyproject.toml +0 -0
  19. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/__init__.py +0 -0
  20. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/__init__.py +0 -0
  21. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/aliceblue.py +0 -0
  22. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/angelone.py +0 -0
  23. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/auto_login/__init__.py +0 -0
  24. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/auto_login/aliceblue.py +0 -0
  25. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/breeze/__init__.py +0 -0
  26. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/breeze/breeze_utils.py +0 -0
  27. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/dhan.py +0 -0
  28. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/finvasia_utils/__init__.py +0 -0
  29. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
  30. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/five_paisa.py +0 -0
  31. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/flattrade.py +0 -0
  32. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/ft_utils/__init__.py +0 -0
  33. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/ft_utils/flattrade_utils.py +0 -0
  34. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/ft_utils/ft_noren.py +0 -0
  35. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/generics/__init__.py +0 -0
  36. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/generics/broker.py +0 -0
  37. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/iifl_xts.py +0 -0
  38. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/jainam_xts.py +0 -0
  39. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/kite_utils.py +0 -0
  40. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/motilal.py +0 -0
  41. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/shoonya.py +0 -0
  42. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/uplink/__init__.py +0 -0
  43. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/uplink/uplink_utils.py +0 -0
  44. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/upstox.py +0 -0
  45. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/xts.py +0 -0
  46. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/xts_utils/Connect.py +0 -0
  47. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/xts_utils/Exception.py +0 -0
  48. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
  49. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/xts_utils/__init__.py +0 -0
  50. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/broker/zerodha.py +0 -0
  51. {quantplay-2.0.178/quantplay/model → quantplay-2.0.181/quantplay/core}/__init__.py +0 -0
  52. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/exception/__init__.py +0 -0
  53. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/exception/exceptions.py +0 -0
  54. {quantplay-2.0.178/quantplay/wrapper → quantplay-2.0.181/quantplay/indicator}/__init__.py +0 -0
  55. {quantplay-2.0.178/quantplay/wrapper/aws → quantplay-2.0.181/quantplay/model}/__init__.py +0 -0
  56. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/model/broker.py +0 -0
  57. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/model/broker_response.py +0 -0
  58. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/model/generics.py +0 -0
  59. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/model/instrument_data.py +0 -0
  60. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/model/order_event.py +0 -0
  61. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/py.typed +0 -0
  62. {quantplay-2.0.178/tests → quantplay-2.0.181/quantplay/strategy}/__init__.py +0 -0
  63. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/__init__.py +0 -0
  64. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/caching.py +0 -0
  65. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/constant.py +0 -0
  66. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/exchange.py +0 -0
  67. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/number_utils.py +0 -0
  68. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/pickle_utils.py +0 -0
  69. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/utils/selenium_utils.py +0 -0
  70. {quantplay-2.0.178/tests → quantplay-2.0.181/quantplay}/wrapper/__init__.py +0 -0
  71. {quantplay-2.0.178/tests → quantplay-2.0.181/quantplay}/wrapper/aws/__init__.py +0 -0
  72. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay/wrapper/aws/s3.py +0 -0
  73. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay.egg-info/dependency_links.txt +0 -0
  74. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay.egg-info/requires.txt +0 -0
  75. {quantplay-2.0.178 → quantplay-2.0.181}/quantplay.egg-info/top_level.txt +0 -0
  76. {quantplay-2.0.178 → quantplay-2.0.181}/setup.cfg +0 -0
  77. /quantplay-2.0.178/tests/conftest.py → /quantplay-2.0.181/tests/__init__.py +0 -0
  78. {quantplay-2.0.178 → quantplay-2.0.181}/tests/wrapper/aws/s3_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.178
3
+ Version: 2.0.181
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -182,6 +182,7 @@ class BrokerFactory:
182
182
 
183
183
  elif broker == Broker.KOTAK:
184
184
  broker_client = Kotak(
185
+ user_id=broker_data["user_id"],
185
186
  consumer_key=broker_data["consumer_key"],
186
187
  consumer_secret=broker_data["consumer_secret"],
187
188
  mobilenumber=broker_data["mobilenumber"],
@@ -384,6 +385,7 @@ class BrokerFactory:
384
385
 
385
386
  elif broker == Broker.KOTAK:
386
387
  broker_client = Kotak(
388
+ user_id=broker_data["user_id"],
387
389
  configuration=broker_data["configuration"],
388
390
  otp=broker_data.get("otp"),
389
391
  load_instrument=load_instrument,
@@ -417,7 +417,7 @@ class ICICI(Broker):
417
417
  margins = self.invoke_wrapper(self.wrapper.get_funds)
418
418
 
419
419
  return {
420
- "total_balance": margins["total_bank_balance"],
420
+ "total_balance": float(margins["total_bank_balance"]),
421
421
  "margin_available": (
422
422
  float(margins["allocated_equity"])
423
423
  + float(margins["allocated_fno"])
@@ -430,7 +430,7 @@ class ICICI(Broker):
430
430
  + float(margins["block_by_trade_commodity"])
431
431
  + float(margins["block_by_trade_currency"])
432
432
  ),
433
- "cash": margins["unallocated_balance"],
433
+ "cash": float(margins["unallocated_balance"]),
434
434
  }
435
435
 
436
436
  def ltp(self, exchange: ExchangeType, tradingsymbol: str) -> float:
@@ -10,6 +10,7 @@ import urllib
10
10
  import jwt
11
11
 
12
12
  from quantplay.broker.generics.broker import Broker
13
+ from quantplay.broker.kotak_utils.kotak_ws import NeoWebSocket
13
14
  from quantplay.model.broker import (
14
15
  MarginsResponse,
15
16
  ModifyOrderRequest,
@@ -20,6 +21,7 @@ from quantplay.model.generics import (
20
21
  OrderTypeType,
21
22
  ProductType,
22
23
  TransactionType,
24
+ OrderStatusType,
23
25
  )
24
26
  from quantplay.model.order_event import OrderUpdateEvent
25
27
  from quantplay.exception.exceptions import (
@@ -40,6 +42,7 @@ class Kotak(Broker):
40
42
 
41
43
  def __init__(
42
44
  self,
45
+ user_id: str,
43
46
  order_updates: Queue[OrderUpdateEvent] | None = None,
44
47
  consumer_key: str | None = None,
45
48
  consumer_secret: str | None = None,
@@ -76,6 +79,8 @@ class Kotak(Broker):
76
79
 
77
80
  if load_instrument:
78
81
  self.load_instrument()
82
+ self.order_updates = order_updates
83
+ self.user_id = user_id
79
84
 
80
85
  def load_instrument(self, file_name: str | None = None) -> None:
81
86
  try:
@@ -573,6 +578,25 @@ class Kotak(Broker):
573
578
 
574
579
  return order_type_map.get(order_type, order_type)
575
580
 
581
+ def get_quantplay_order_type(self, order_type: str) -> OrderTypeType:
582
+ order_type_map: dict[str, str] = {
583
+ "L": "LIMIT",
584
+ "MKT": "MARKET",
585
+ }
586
+
587
+ return order_type_map.get(order_type, order_type) # type:ignore
588
+
589
+ def get_quantplay_order_status(self, status: str) -> OrderStatusType:
590
+ if status in ["put order req received", "validation pending", "open pending"]:
591
+ return "OPEN"
592
+ if status == "cancel pending":
593
+ return "CANCELLED"
594
+ status = status.upper()
595
+ if status not in ["OPEN", "TRIGGER_PENDING", "CANCELLED", "REJECTED"]:
596
+ Constants.logger.error(f"{status} not supported for Kotak")
597
+ return "INVALID STATUS" # type:ignore
598
+ return status # type:ignore
599
+
576
600
  def get_exchange(self, exchange: ExchangeType) -> str:
577
601
  exchange_segment_map: dict[ExchangeType, str] = {
578
602
  "NSE": "nse_cm",
@@ -739,3 +763,51 @@ class Kotak(Broker):
739
763
  raise BrokerException("Request Failed: No response from Kotak server")
740
764
 
741
765
  return resp_data
766
+
767
+ def event_handler_order_update(self, order: dict[str, Any]):
768
+ if "data" not in order:
769
+ return
770
+ data = json.loads(order["data"])
771
+ if data["type"] != "order":
772
+ return
773
+ order = data["data"]
774
+
775
+ quantplay_order: OrderUpdateEvent = {
776
+ "placed_by": self.user_id, # type:ignore
777
+ "tradingsymbol": order["trdSym"],
778
+ "exchange": self.get_quantplay_exchange(order["exSeg"]),
779
+ "tag": order["GuiOrdId"],
780
+ "order_id": order["nOrdNo"],
781
+ "exchange_order_id": order["exOrdId"],
782
+ "order_type": self.get_quantplay_order_type(order["prcTp"]),
783
+ "price": float(order["prc"]),
784
+ "quantity": int(order["qty"]),
785
+ "product": order["prod"],
786
+ "status": self.get_quantplay_order_status(order["ordSt"]),
787
+ }
788
+
789
+ if quantplay_order["exchange"] == "NSE":
790
+ quantplay_order["tradingsymbol"] = quantplay_order["tradingsymbol"].replace(
791
+ "-EQ", ""
792
+ )
793
+
794
+ quantplay_order["transaction_type"] = "BUY" if order["trnsTp"] == "B" else "SELL"
795
+ if "trgPrc" in order:
796
+ quantplay_order["trigger_price"] = float(order["trgPrc"])
797
+ else:
798
+ quantplay_order["trigger_price"] = None
799
+ print(quantplay_order)
800
+
801
+ if self.order_updates:
802
+ self.order_updates.put(quantplay_order)
803
+
804
+ def stream_order_data(self):
805
+ ws = NeoWebSocket(
806
+ sid=self.configuration.get("sid", ""),
807
+ token=self.configuration.get("edit_token", ""),
808
+ server_id=self.configuration.get("serverId", ""),
809
+ )
810
+ ws.on_message = self.event_handler_order_update
811
+ ws.on_error = lambda x: print(x)
812
+
813
+ ws.get_order_feed()
@@ -764,7 +764,7 @@ class Noren(Broker):
764
764
  cash = 0
765
765
 
766
766
  margins: MarginsResponse = {
767
- "margin_used": api_margins["margin_used"],
767
+ "margin_used": float(api_margins["margin_used"]),
768
768
  "margin_available": margin_available,
769
769
  "cash": cash,
770
770
  "total_balance": float(cash) + float(holdings_val),
@@ -0,0 +1,193 @@
1
+ from datetime import datetime
2
+ import polars as pl
3
+ import logging
4
+
5
+ pl.Config.set_tbl_cols(10)
6
+
7
+
8
+ class Strategy:
9
+ def __init__(self, name: str = "BaseStrategy") -> None:
10
+ self.index: str
11
+ self.type: str
12
+ self.exit_time: str
13
+ self.index: str
14
+
15
+ self.name = name
16
+ self.underlying_map = {"NIFTY BANK": "BANKNIFTY"}
17
+ self.data_source = "~/.quantplay"
18
+ self.symbol_expiry = pl.read_parquet(f"{self.data_source}/symbol_expiry.parquet")
19
+
20
+ logging.basicConfig(level=logging.INFO)
21
+
22
+ self.logger = logging.getLogger("testing")
23
+
24
+ def add_days_to_expiry(self, trades: pl.DataFrame) -> pl.DataFrame:
25
+ trades = trades.join(self.symbol_expiry, on=["symbol"], how="left")
26
+ trades = trades.with_columns(
27
+ pl.col("expiry")
28
+ .sub(pl.col("date_only"))
29
+ .dt.total_days()
30
+ .alias("days_to_expiry")
31
+ )
32
+
33
+ return trades
34
+
35
+ def backtest(self, trades: pl.DataFrame):
36
+ """
37
+ Backtest the strategy using historical data.
38
+
39
+ Parameters:
40
+ data (pl.DataFrame): Historical OHLC data.
41
+
42
+ Returns:
43
+ pl.DataFrame: DataFrame containing signals and performance metrics.
44
+ """
45
+ underlying_name = self.underlying_map[self.index]
46
+ opt_data = pl.read_parquet(
47
+ f"{self.data_source}/RAW/OPT/{underlying_name}.parquet"
48
+ )
49
+
50
+ if self.type == "intraday":
51
+ trades = trades.select(
52
+ [
53
+ "date",
54
+ "date_only",
55
+ "underlying_price",
56
+ "symbol",
57
+ "close",
58
+ ]
59
+ ).rename({"close": "entry_price", "date": "entry_time"})
60
+
61
+ market_data = (
62
+ opt_data.filter(
63
+ pl.col("date")
64
+ .dt.time()
65
+ .eq(datetime.strptime(self.exit_time, "%H:%M").time())
66
+ )
67
+ .select(["symbol", "date", "close"])
68
+ .with_columns(pl.col("date").dt.date().alias("date_only"))
69
+ .rename({"close": "exit_price", "date": "exit_time"})
70
+ )
71
+
72
+ bt_trades = trades.join(market_data, on=["date_only", "symbol"], how="left")
73
+ bt_trades = bt_trades.with_columns(
74
+ (pl.col("exit_price").sub(pl.col("entry_price")).alias("pnl"))
75
+ ).select(
76
+ [
77
+ "symbol",
78
+ "entry_time",
79
+ "exit_time",
80
+ "entry_price",
81
+ "exit_price",
82
+ "pnl",
83
+ ]
84
+ )
85
+
86
+ print(bt_trades)
87
+ print(bt_trades.select(pl.col("exit_price").sub(pl.col("entry_price"))).sum())
88
+ bt_trades.write_parquet(f"./out/{self.name}.parquet")
89
+
90
+ elif self.type == "overnight":
91
+ trades = (
92
+ trades.with_columns(
93
+ pl.col("date_only")
94
+ .add(
95
+ pl.when(pl.col("date_only").dt.weekday().eq(5))
96
+ .then(pl.duration(days=3))
97
+ .when(pl.col("date_only").dt.weekday().eq(6))
98
+ .then(pl.duration(days=2))
99
+ .otherwise(pl.duration(days=1))
100
+ )
101
+ .alias("next_day")
102
+ )
103
+ .select(
104
+ [
105
+ "date",
106
+ "next_day",
107
+ "date_only",
108
+ "underlying_price",
109
+ "symbol",
110
+ "close",
111
+ ]
112
+ )
113
+ .rename({"close": "entry_price", "date": "entry_time"})
114
+ )
115
+
116
+ market_data = (
117
+ opt_data.filter(pl.col("date").dt.time().eq(pl.time(9, 30, 0)))
118
+ .select(["symbol", "date", "close"])
119
+ .with_columns(pl.col("date").dt.date().alias("next_day"))
120
+ .rename({"close": "exit_price", "date": "exit_time"})
121
+ )
122
+
123
+ bt_trades = trades.join(market_data, on=["next_day", "symbol"], how="left")
124
+
125
+ bt_trades = bt_trades.with_columns(
126
+ (pl.col("exit_price").sub(pl.col("entry_price")).alias("pnl"))
127
+ ).select(
128
+ [
129
+ "symbol",
130
+ "entry_time",
131
+ "exit_time",
132
+ "entry_price",
133
+ "exit_price",
134
+ "pnl",
135
+ ]
136
+ )
137
+
138
+ print(bt_trades)
139
+ print(bt_trades.select(pl.col("exit_price").sub(pl.col("entry_price"))).sum())
140
+
141
+ bt_trades.write_parquet(f"./out/{self.name}.parquet")
142
+
143
+ def calculate_performance(self, data: pl.DataFrame) -> pl.DataFrame:
144
+ """
145
+ Calculate performance metrics based on signals.vv
146
+
147
+ Parameters:
148
+ data (pl.DataFrame): DataFrame with signals.
149
+
150
+ Returns:
151
+ pl.DataFrame: DataFrame with performance metrics.
152
+ """
153
+ # Implement performance calculation
154
+ raise NotImplementedError("Please implement the calculate_performance method.")
155
+
156
+ def load_data(self) -> None:
157
+ self.logger.info("Loading data")
158
+ # Load data based on the provided data format
159
+
160
+ index_data = pl.read_parquet(
161
+ f"{self.data_source}/NSE/INDICES/minute/{self.index}.parquet"
162
+ )
163
+ underlying_name = self.underlying_map[self.index]
164
+ opt_data = pl.read_parquet(
165
+ f"{self.data_source}/RAW/OPT/{underlying_name}.parquet"
166
+ )
167
+ self.data = {"index_data": index_data, "opt_data": opt_data}
168
+ self.logger.info("Data loaded successfully ....")
169
+
170
+ def run_backtest(self, **kwargs: ...):
171
+ """
172
+ Run backtest for a given strategy by loading data internally.
173
+
174
+ Parameters:
175
+ strategy (Strategy): An instance of a Strategy subclass.
176
+ **kwargs: Additional keyword arguments for data loading.
177
+
178
+ Returns:
179
+ pl.DataFrame: DataFrame with backtest results.
180
+ """
181
+ # Set up logging
182
+
183
+ try:
184
+ self.load_data()
185
+ trades = self.generate_signals(self.data)
186
+ self.backtest(trades)
187
+
188
+ self.logger.info("Backtest completed successfully.")
189
+ except Exception as e:
190
+ self.logger.error(f"An error occurred during backtest: {e}")
191
+ raise
192
+
193
+ def generate_signals(self, data: dict[str, pl.DataFrame]) -> pl.DataFrame: ...
@@ -0,0 +1,37 @@
1
+ from py_vollib.black_scholes.implied_volatility import implied_volatility # type:ignore
2
+ from datetime import date, datetime, time
3
+ from typing import Literal
4
+
5
+
6
+ class ImpliedVolatility:
7
+ @staticmethod
8
+ def get_option_type(option_type: Literal["CE", "PE"]) -> Literal["c", "p"]:
9
+ if option_type == "CE":
10
+ return "c"
11
+
12
+ elif option_type == "PE":
13
+ return "p"
14
+
15
+ @staticmethod
16
+ def time_to_expiry(expiry: date, tick_time: float) -> float:
17
+ expiry_dt = datetime.combine(date=expiry, time=time(15, 30, 0, 0))
18
+ time_to_expiry = (expiry_dt.timestamp() - tick_time) / 31536000
19
+
20
+ return time_to_expiry
21
+
22
+ @staticmethod
23
+ def iv(
24
+ option_price: float,
25
+ strike: int,
26
+ option_type: Literal["CE", "PE"],
27
+ underlying_price: float,
28
+ tte: float,
29
+ ) -> float:
30
+ rate_of_interest = 0.1
31
+
32
+ return (
33
+ implied_volatility( # type:ignore
34
+ option_price, underlying_price, strike, tte, rate_of_interest, option_type
35
+ )
36
+ * 100
37
+ )
@@ -0,0 +1,93 @@
1
+ import polars as pl
2
+ from datetime import time
3
+
4
+ from quantplay.core.strategy import Strategy
5
+ from quantplay.indicator.iv import ImpliedVolatility
6
+
7
+
8
+ class IVSpike(Strategy):
9
+ def __init__(self) -> None:
10
+ super().__init__(name="iv_spike")
11
+ self.type = "intraday"
12
+ self.exit_time = "15:10"
13
+ self.index = "NIFTY BANK"
14
+
15
+ def generate_signals(self, data: dict[str, pl.DataFrame]) -> pl.DataFrame:
16
+ """
17
+ Generate signals based on below logic
18
+ """
19
+ index_data = data["index_data"]
20
+ opt_data = data["opt_data"]
21
+
22
+ index_data = index_data.with_columns(pl.col("close").alias("underlying_price"))
23
+
24
+ opt_data = opt_data.with_columns(pl.col("date").dt.date().alias("date_only"))
25
+ opt_data = self.add_days_to_expiry(opt_data)
26
+
27
+ opt_data = opt_data.filter(pl.col("days_to_expiry").lt(2))
28
+ opt_data = opt_data.join(
29
+ index_data["date", "underlying_price"],
30
+ on=["date"],
31
+ how="left",
32
+ )
33
+ opt_data = opt_data.filter(
34
+ pl.col("date").dt.time() <= pl.time(hour=14, minute=45)
35
+ )
36
+ opt_data = opt_data.filter(pl.col("date").dt.year() >= 2024)
37
+ opt_data = opt_data.filter(pl.col("date").dt.month() >= 8)
38
+ print(len(opt_data))
39
+
40
+ opt_data = opt_data.with_columns(
41
+ pl.col("symbol")
42
+ .str.extract(r"[A-Z]+[0-9]{2}.{3}([0-9]+)[P|C]E")
43
+ .cast(pl.Float64)
44
+ .alias("strike"),
45
+ pl.col("symbol")
46
+ .str.extract(r"[A-Z]+[0-9]{2}.{3}[0-9]+([P|C])E")
47
+ .str.to_lowercase()
48
+ .alias("option_type"),
49
+ (
50
+ (pl.col("expiry").dt.combine(time(15, 30, 0)).dt.timestamp("ms"))
51
+ .sub(pl.col("date").dt.timestamp("ms"))
52
+ .truediv(31536000000)
53
+ ).alias("tte"),
54
+ )
55
+
56
+ opt_data = opt_data.with_columns(
57
+ pl.struct(
58
+ [
59
+ "close",
60
+ "strike",
61
+ "option_type",
62
+ "underlying_price",
63
+ "tte",
64
+ ]
65
+ )
66
+ .map_elements(
67
+ lambda x: int(
68
+ ImpliedVolatility.iv(
69
+ x["close"],
70
+ x["strike"],
71
+ x["option_type"],
72
+ x["underlying_price"],
73
+ x["tte"],
74
+ )
75
+ ),
76
+ return_dtype=pl.Float64,
77
+ )
78
+ .alias("iv")
79
+ )
80
+
81
+ trades = opt_data
82
+ trades = trades.filter(pl.col("iv") > 40).filter(
83
+ pl.col("close").sub(30).abs().rank("dense").eq(1).over("date_only")
84
+ )
85
+
86
+ trades = trades.with_columns(pl.lit("SELL").alias("transaction_type"))
87
+ print(trades)
88
+ return trades
89
+
90
+
91
+ if __name__ == "__main__":
92
+ self = IVSpike()
93
+ self.run_backtest()
@@ -0,0 +1,51 @@
1
+ import polars as pl
2
+
3
+ from quantplay.core.strategy import Strategy
4
+
5
+
6
+ class OvernightOptionBuy(Strategy):
7
+ def __init__(self) -> None:
8
+ super().__init__(name="OBuy")
9
+ self.type = "overnight"
10
+ self.exit_time = "09:30"
11
+ self.index = "NIFTY BANK"
12
+
13
+ def generate_signals(self, data: dict[str, pl.DataFrame]) -> pl.DataFrame:
14
+ """
15
+ Generate signals based on below logic
16
+ """
17
+ index_data = data["index_data"]
18
+ opt_data = data["opt_data"]
19
+
20
+ index_data = index_data.filter(
21
+ pl.col("date").dt.time() >= pl.time(hour=9, minute=30)
22
+ )
23
+ index_data = index_data.with_columns(pl.col("date").dt.date().alias("date_only"))
24
+ index_data = index_data.with_columns(
25
+ pl.col("close").cum_max().over("date_only").alias("intraday_high")
26
+ )
27
+ index_data = index_data.filter(
28
+ (pl.col("date").dt.time() >= pl.time(hour=13, minute=30))
29
+ & (pl.col("close") >= pl.col("intraday_high"))
30
+ )
31
+ trades = index_data.group_by("date_only").agg(pl.all().first())
32
+ trades = trades.with_columns(pl.col("close").alias("underlying_price"))
33
+
34
+ trades = trades[["date", "date_only", "underlying_price"]].join(
35
+ opt_data,
36
+ on=["date"],
37
+ how="left",
38
+ )
39
+
40
+ trades = self.add_days_to_expiry(trades)
41
+
42
+ trades = trades.filter(
43
+ (pl.col("days_to_expiry") < 7) & pl.col("days_to_expiry").gt(3)
44
+ ).filter(pl.col("close").sub(700).abs().rank("dense").eq(1).over("date_only"))
45
+
46
+ return trades
47
+
48
+
49
+ if __name__ == "__main__":
50
+ self = OvernightOptionBuy()
51
+ self.run_backtest()
@@ -39,8 +39,8 @@ class Redis:
39
39
 
40
40
  Redis.__instance = self
41
41
 
42
- def get(self, key: str) -> None:
42
+ def get(self, key: int) -> None:
43
43
  if self.error:
44
44
  return None
45
45
 
46
- return self.redis_client.get(str(key)) # type: ignore
46
+ return self.redis_client.get(f"mkt:{key}") # type: ignore
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.178
3
+ Version: 2.0.181
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -43,14 +43,21 @@ quantplay/broker/xts_utils/Connect.py
43
43
  quantplay/broker/xts_utils/Exception.py
44
44
  quantplay/broker/xts_utils/InteractiveSocketClient.py
45
45
  quantplay/broker/xts_utils/__init__.py
46
+ quantplay/core/__init__.py
47
+ quantplay/core/strategy.py
46
48
  quantplay/exception/__init__.py
47
49
  quantplay/exception/exceptions.py
50
+ quantplay/indicator/__init__.py
51
+ quantplay/indicator/iv.py
48
52
  quantplay/model/__init__.py
49
53
  quantplay/model/broker.py
50
54
  quantplay/model/broker_response.py
51
55
  quantplay/model/generics.py
52
56
  quantplay/model/instrument_data.py
53
57
  quantplay/model/order_event.py
58
+ quantplay/strategy/__init__.py
59
+ quantplay/strategy/iv_spike.py
60
+ quantplay/strategy/obuy.py
54
61
  quantplay/utils/__init__.py
55
62
  quantplay/utils/caching.py
56
63
  quantplay/utils/constant.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.0.178",
24
+ version="2.0.181",
25
25
  setup_requires=["pytest-runner"],
26
26
  install_requires=requirements,
27
27
  tests_require=[],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes