quantplay 2.0.28__tar.gz → 2.0.30__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 (71) hide show
  1. {quantplay-2.0.28 → quantplay-2.0.30}/PKG-INFO +1 -1
  2. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/aliceblue.py +41 -23
  3. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/angelone.py +1 -1
  4. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/five_paisa.py +2 -1
  5. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/generics/broker.py +25 -0
  6. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/motilal.py +9 -6
  7. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/noren.py +8 -4
  8. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/upstox.py +2 -1
  9. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/xts.py +10 -2
  10. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay.egg-info/PKG-INFO +1 -1
  11. {quantplay-2.0.28 → quantplay-2.0.30}/setup.py +1 -1
  12. {quantplay-2.0.28 → quantplay-2.0.30}/README.md +0 -0
  13. {quantplay-2.0.28 → quantplay-2.0.30}/pyproject.toml +0 -0
  14. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/__init__.py +0 -0
  15. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/__init__.py +0 -0
  16. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/auto_login/__init__.py +0 -0
  17. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/auto_login/aliceblue.py +0 -0
  18. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/finvasia_utils/__init__.py +0 -0
  19. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
  20. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/flattrade.py +0 -0
  21. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/ft_utils/__init__.py +0 -0
  22. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/ft_utils/flattrade_utils.py +0 -0
  23. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/ft_utils/ft_noren.py +0 -0
  24. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/generics/__init__.py +0 -0
  25. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/iifl_xts.py +0 -0
  26. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/kite_utils.py +0 -0
  27. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/shoonya.py +0 -0
  28. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/uplink/__init__.py +0 -0
  29. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/uplink/uplink_utils.py +0 -0
  30. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/xts_utils/Connect.py +0 -0
  31. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/xts_utils/Exception.py +0 -0
  32. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
  33. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/xts_utils/__init__.py +0 -0
  34. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/broker/zerodha.py +0 -0
  35. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/exception/__init__.py +0 -0
  36. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/exception/exceptions.py +0 -0
  37. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/model/__init__.py +0 -0
  38. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/model/broker.py +0 -0
  39. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/model/generics.py +0 -0
  40. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/model/order_event.py +0 -0
  41. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/py.typed +0 -0
  42. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/__init__.py +0 -0
  43. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/equities/__init__.py +0 -0
  44. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/equities/intraday/__init__.py +0 -0
  45. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/equities/overnight/__init__.py +0 -0
  46. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/futures/__init__.py +0 -0
  47. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/futures/overnight/__init__.py +0 -0
  48. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/options/__init__.py +0 -0
  49. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/options/intraday/__init__.py +0 -0
  50. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/options/intraday/ladder.py +0 -0
  51. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/options/intraday/musk.py +0 -0
  52. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/strategies/options/intraday/short_straddle.py +0 -0
  53. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/utils/__init__.py +0 -0
  54. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/utils/constant.py +0 -0
  55. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/utils/exchange.py +0 -0
  56. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/utils/number_utils.py +0 -0
  57. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/utils/pickle_utils.py +0 -0
  58. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/utils/selenium_utils.py +0 -0
  59. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/wrapper/__init__.py +0 -0
  60. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/wrapper/aws/__init__.py +0 -0
  61. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay/wrapper/aws/s3.py +0 -0
  62. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay.egg-info/SOURCES.txt +0 -0
  63. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay.egg-info/dependency_links.txt +0 -0
  64. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay.egg-info/requires.txt +0 -0
  65. {quantplay-2.0.28 → quantplay-2.0.30}/quantplay.egg-info/top_level.txt +0 -0
  66. {quantplay-2.0.28 → quantplay-2.0.30}/setup.cfg +0 -0
  67. {quantplay-2.0.28 → quantplay-2.0.30}/tests/__init__.py +0 -0
  68. {quantplay-2.0.28 → quantplay-2.0.30}/tests/conftest.py +0 -0
  69. {quantplay-2.0.28 → quantplay-2.0.30}/tests/wrapper/__init__.py +0 -0
  70. {quantplay-2.0.28 → quantplay-2.0.30}/tests/wrapper/aws/__init__.py +0 -0
  71. {quantplay-2.0.28 → quantplay-2.0.30}/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.28
3
+ Version: 2.0.30
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -3,13 +3,13 @@ import copy
3
3
  import pickle
4
4
  from queue import Queue
5
5
  import traceback
6
- from typing import Any, Callable, Literal
6
+ from typing import Any, Callable, Dict, Literal
7
7
 
8
8
  import polars as pl
9
9
  from pya3 import Aliceblue as Alice
10
10
  from pya3 import OrderType as AliceOrderType
11
11
  from pya3 import ProductType, TransactionType
12
- from retrying import retry # type: ignore
12
+ from retrying import retry
13
13
 
14
14
  from quantplay.broker.generics.broker import Broker
15
15
  from quantplay.exception.exceptions import (
@@ -19,8 +19,8 @@ from quantplay.exception.exceptions import (
19
19
  TokenException,
20
20
  retry_exception,
21
21
  )
22
- from quantplay.exception.exceptions import TokenException as QuantplayTokenException
23
22
  from quantplay.model.broker import ModifyOrderRequest, UserBrokerProfileResponse
23
+ from quantplay.model.generics import ExchangeType
24
24
  from quantplay.model.order_event import OrderUpdateEvent
25
25
  from quantplay.utils.constant import Constants, OrderType
26
26
  from quantplay.utils.pickle_utils import InstrumentData
@@ -94,8 +94,10 @@ class Aliceblue(Broker):
94
94
  if exchange == "NSE":
95
95
  if "-EQ" not in symbol:
96
96
  return f"{symbol}-EQ"
97
+
97
98
  elif symbol not in self.quantplay_symbol_map:
98
99
  return symbol
100
+
99
101
  return self.quantplay_symbol_map[symbol]
100
102
 
101
103
  def load_instrument(self, file_name: str | None = None):
@@ -213,11 +215,20 @@ class Aliceblue(Broker):
213
215
  exception_message = f"Order placement failed [{str(e)}]"
214
216
  raise QuantplayOrderPlacementException(exception_message)
215
217
 
216
- def ltp(self, exchange, tradingsymbol: str):
218
+ def ltp(self, exchange: ExchangeType, tradingsymbol: str) -> float:
217
219
  tradingsymbol = self.get_symbol(tradingsymbol, exchange=exchange)
218
- inst = self.invoke_aliceblue_api(
219
- self.alice.get_instrument_by_symbol, exchange=exchange, symbol=tradingsymbol
220
- )
220
+ try:
221
+ inst = self.invoke_aliceblue_api(
222
+ self.alice.get_instrument_by_symbol,
223
+ exchange=exchange,
224
+ symbol=tradingsymbol,
225
+ )
226
+ except Exception:
227
+ inst = self.invoke_aliceblue_api(
228
+ self.alice.get_instrument_by_symbol,
229
+ exchange=exchange,
230
+ symbol=tradingsymbol.replace("-EQ", ""),
231
+ )
221
232
  info = self.invoke_aliceblue_api(self.alice.get_scrip_info, instrument=inst)
222
233
 
223
234
  return float(info["LTP"])
@@ -308,7 +319,8 @@ class Aliceblue(Broker):
308
319
  holdings_response = self.invoke_aliceblue_api(self.alice.get_holding_positions)
309
320
 
310
321
  if (
311
- "HoldingVal" not in holdings_response
322
+ holdings_response is None
323
+ or "HoldingVal" not in holdings_response
312
324
  or len(holdings_response["HoldingVal"]) == 0
313
325
  ):
314
326
  return pl.DataFrame(schema=self.holidings_schema)
@@ -325,7 +337,9 @@ class Aliceblue(Broker):
325
337
  "HUqty": "quantity",
326
338
  },
327
339
  )
328
-
340
+ holdings_df = holdings_df.with_columns(
341
+ pl.col("tradingsymbol").str.replace("-EQ", "").alias("tradingsymbol")
342
+ )
329
343
  holdings_df = holdings_df.with_columns(pl.lit("NSE").alias("exchange"))
330
344
  holdings_df = holdings_df.with_columns(
331
345
  pl.struct(["exchange", "tradingsymbol"])
@@ -335,7 +349,6 @@ class Aliceblue(Broker):
335
349
  )
336
350
  .alias("price")
337
351
  )
338
-
339
352
  holdings_df = holdings_df.with_columns(
340
353
  pl.col("quantity").cast(pl.Int64).alias("quantity"),
341
354
  pl.col("average_price").cast(pl.Float64).alias("average_price"),
@@ -417,13 +430,7 @@ class Aliceblue(Broker):
417
430
  def orders(self, tag: str | None = None, add_ltp: bool = True) -> pl.DataFrame:
418
431
  orders_response = self.invoke_aliceblue_api(self.alice.order_data)
419
432
 
420
- if not isinstance(orders_response, list) and (
421
- ("no data" in orders_response["emsg"].lower())
422
- or (
423
- "401" in orders_response["emsg"].lower()
424
- or ("404" in orders_response["emsg"])
425
- )
426
- ):
433
+ if not isinstance(orders_response, list):
427
434
  return pl.DataFrame(schema=self.orders_schema)
428
435
 
429
436
  if len(orders_response) == 0:
@@ -549,7 +556,7 @@ class Aliceblue(Broker):
549
556
 
550
557
  return orders_df[list(self.orders_schema.keys())].cast(self.orders_schema)
551
558
 
552
- def margins(self):
559
+ def margins(self) -> Dict[str, float]:
553
560
  margins = self.invoke_aliceblue_api(self.alice.get_balance)
554
561
 
555
562
  margins = [a for a in margins if a["segment"] == "ALL"][0]
@@ -567,20 +574,31 @@ class Aliceblue(Broker):
567
574
 
568
575
  return margins
569
576
 
570
- # TODO
571
577
  def invoke_aliceblue_api(self, fn: Callable, *args, **kwargs) -> Any:
572
578
  try:
573
579
  response = fn(*args, **kwargs)
574
580
 
575
- if "emsg" in response and "expired" in response["emsg"].lower():
576
- raise TokenException("Session expired")
577
- if "stat" in response and response["stat"].lower() == "not_ok":
578
- raise InvalidArgumentException(response["emsg"])
579
581
  if response is None:
580
582
  raise InvalidArgumentException(
581
583
  "Invalid data response. Aliceblue sent incorrect data, Please check."
582
584
  )
583
585
 
586
+ if "emsg" in response and "expired" in response["emsg"].lower():
587
+ raise TokenException("Session expired")
588
+ elif (
589
+ "stat" in response
590
+ and response["stat"].lower() == "not_ok"
591
+ and (
592
+ "no data" in response["emsg"]
593
+ or "401" in response["emsg"]
594
+ or "404" in response["emsg"]
595
+ )
596
+ ):
597
+ return None
598
+
599
+ elif "stat" in response and response["stat"].lower() == "not_ok":
600
+ raise InvalidArgumentException(response["emsg"])
601
+
584
602
  return response
585
603
 
586
604
  except (TokenException, InvalidArgumentException):
@@ -513,7 +513,7 @@ class AngelOne(Broker):
513
513
 
514
514
  return response
515
515
 
516
- def margins(self):
516
+ def margins(self) -> Dict[str, float]:
517
517
  api_margins = self.invoke_angelone_api(self.wrapper.rmsLimit)
518
518
 
519
519
  if "data" in api_margins and api_margins["data"] is None:
@@ -1,6 +1,7 @@
1
1
  import codecs
2
2
  import pickle
3
3
  import traceback
4
+ from typing import Dict
4
5
 
5
6
  import polars as pl
6
7
  import pyotp
@@ -509,7 +510,7 @@ class FivePaisa(Broker):
509
510
  stop_max_attempt_number=3,
510
511
  retry_on_exception=retry_exception,
511
512
  )
512
- def margins(self):
513
+ def margins(self) -> Dict[str, float]:
513
514
  margins = self.client.margin()
514
515
  if margins is None:
515
516
  raise TokenException("5Paisa Token expired")
@@ -1,5 +1,6 @@
1
1
  import copy
2
2
  import io
3
+ import json
3
4
  import math
4
5
  import os
5
6
  import platform
@@ -42,6 +43,13 @@ from quantplay.wrapper.aws.s3 import S3Utils
42
43
 
43
44
  logger = Constants.logger
44
45
 
46
+ status_to_log_map = {
47
+ "error": logger.error,
48
+ "info": logger.info,
49
+ "warn": logger.warn,
50
+ "debug": logger.debug,
51
+ }
52
+
45
53
 
46
54
  class Broker:
47
55
  def __init__(self):
@@ -1155,6 +1163,23 @@ class Broker:
1155
1163
  "Max Modification Limit Exceeded : [{}]".format(order_id)
1156
1164
  )
1157
1165
 
1166
+ def log_event(
1167
+ self,
1168
+ event: Dict[str, Any],
1169
+ status: Literal["error", "info", "warn", "debug"] = "info",
1170
+ ) -> None:
1171
+ """Logs Strategy Events in Standard Format
1172
+
1173
+ Format: [username][strategy_name][event_type]: [event as JSON String]
1174
+
1175
+ Args:
1176
+ event (Dict[str, str]): Event as a Dictionary
1177
+ status (Literal['error', 'info', 'warn', 'debug'], optional): Logging Level. Defaults to 'info'.
1178
+ """
1179
+ status_to_log_map[status](
1180
+ f"[{self.username}][{self.nickname}][{self.broker_name}] {json.dumps(event, default=Constants.myconverter)}"
1181
+ )
1182
+
1158
1183
  # **
1159
1184
  # ** Generics
1160
1185
  # **
@@ -273,10 +273,10 @@ class Motilal(Broker):
273
273
  "scripcode": token,
274
274
  }
275
275
 
276
- Constants.logger.info("[GET_LTP_REQUEST] response {}".format(data))
277
- response = self.__post_request(self.ltp_url, json.dumps(data))
278
- Constants.logger.info("[GET_LTP_RESPONSE] response {}".format(response.json()))
279
- return response.json()["data"]["ltp"] / 100.0
276
+ Constants.logger.info(f"[GET_LTP_REQUEST] response {data}")
277
+ response = self.__post_request(self.ltp_url, data)
278
+ Constants.logger.info(f"[GET_LTP_RESPONSE] response {response}")
279
+ return response["data"]["ltp"] / 100.0
280
280
 
281
281
  def modify_price(
282
282
  self,
@@ -376,7 +376,7 @@ class Motilal(Broker):
376
376
  )
377
377
  Constants.logger.error(exception_message)
378
378
 
379
- def margins(self):
379
+ def margins(self) -> Dict[str, float]:
380
380
  response = self.__post_request(self.margin_summary_url, {})
381
381
  margin_summary = response["data"]
382
382
 
@@ -533,10 +533,13 @@ class Motilal(Broker):
533
533
  retry_on_exception=retry_exception,
534
534
  )
535
535
  def __post_request(self, url, data):
536
+ if not data:
537
+ data = {"Clientcode": self.headers["vendorinfo"]}
538
+ data = json.dumps(data)
536
539
  api_response = requests.post(
537
540
  url,
538
541
  headers=self.headers,
539
- data=json.dumps({"Clientcode": self.headers["vendorinfo"]}),
542
+ data=data,
540
543
  ).json()
541
544
 
542
545
  if api_response["status"] == "ERROR" and api_response["errorcode"] in [
@@ -251,7 +251,13 @@ class Noren(Broker):
251
251
  def ltp(self, exchange, tradingsymbol):
252
252
  tradingsymbol = self.get_symbol(tradingsymbol, exchange)
253
253
 
254
- token = self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["token"]
254
+ try:
255
+ token = self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["token"]
256
+ except KeyError:
257
+ token = self.symbol_data["{}:{}".format(exchange, tradingsymbol[:-3])][
258
+ "token"
259
+ ]
260
+
255
261
  quote = self.api.get_quotes(exchange, str(token))
256
262
 
257
263
  if quote is None:
@@ -300,7 +306,6 @@ class Noren(Broker):
300
306
  order_id = order_to_modify["order_id"]
301
307
  existing_details = self.order_history(order_id)
302
308
 
303
- # TODO
304
309
  order_to_modify["order_type"] = self.get_order_type(order_to_modify["order_type"]) # type: ignore
305
310
 
306
311
  if "trigger_price" not in order_to_modify:
@@ -663,7 +668,6 @@ class Noren(Broker):
663
668
  response = fn(*args, **kwargs)
664
669
  if response is None:
665
670
  return response
666
- # TODO: Verify
667
671
  if "stat" in response and "not_ok" == response["stat"].lower():
668
672
  raise TokenException(response["emsg"] if response is not None else "")
669
673
 
@@ -683,7 +687,7 @@ class Noren(Broker):
683
687
  stop_max_attempt_number=3,
684
688
  retry_on_exception=retry_exception,
685
689
  )
686
- def margins(self) -> Dict:
690
+ def margins(self) -> Dict[str, float]:
687
691
  api_margins = self.invoke_noren_api(self.api.get_limits)
688
692
 
689
693
  try:
@@ -3,6 +3,7 @@ import json
3
3
  import ssl
4
4
  import threading
5
5
  import traceback
6
+ from typing import Dict
6
7
 
7
8
  import polars as pl
8
9
  import upstox_client
@@ -550,7 +551,7 @@ class Upstox(Broker):
550
551
  stop_max_attempt_number=3,
551
552
  retry_on_exception=retry_exception,
552
553
  )
553
- def margins(self):
554
+ def margins(self) -> Dict[str, float]:
554
555
  api_instance = upstox_client.UserApi(self.api_client)
555
556
 
556
557
  segment = "SEC" # str | (optional)
@@ -4,6 +4,7 @@ import pickle
4
4
  import traceback
5
5
  from datetime import datetime
6
6
  from queue import Queue
7
+ from typing import Dict
7
8
 
8
9
  import pandas as pd
9
10
  import polars as pl
@@ -15,6 +16,7 @@ from quantplay.broker.generics.broker import Broker
15
16
  from quantplay.broker.xts_utils.Connect import XTSConnect
16
17
  from quantplay.broker.xts_utils.InteractiveSocketClient import OrderSocket_io
17
18
  from quantplay.exception.exceptions import (
19
+ BrokerException,
18
20
  InvalidArgumentException,
19
21
  RetryableException,
20
22
  TokenException,
@@ -175,7 +177,7 @@ class XTS(Broker):
175
177
  stop_max_attempt_number=3,
176
178
  retry_on_exception=retry_exception,
177
179
  )
178
- def margins(self) -> dict:
180
+ def margins(self) -> Dict[str, float]:
179
181
  # TODO: Edit For Dealers
180
182
 
181
183
  api_response = self.wrapper.get_balance(clientID=self.ClientID)
@@ -553,8 +555,14 @@ class XTS(Broker):
553
555
  ],
554
556
  xtsMessageCode=1512,
555
557
  publishFormat="JSON",
556
- )["result"]
558
+ )
557
559
 
560
+ if api_response["type"] == "error":
561
+ raise BrokerException(
562
+ api_response.get("description", "Broker Failed to Provide LTP")
563
+ )
564
+
565
+ api_response = api_response["result"]
558
566
  ltp_json = api_response["listQuotes"][0]
559
567
 
560
568
  ltp = json.loads(ltp_json)["LastTradedPrice"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.28
3
+ Version: 2.0.30
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -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.28",
24
+ version="2.0.30",
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