tradingapi 0.1.2__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.
- tradingapi-0.1.2/PKG-INFO +14 -0
- tradingapi-0.1.2/pyproject.toml +47 -0
- tradingapi-0.1.2/setup.cfg +4 -0
- tradingapi-0.1.2/tradingapi/__init__.py +83 -0
- tradingapi-0.1.2/tradingapi/broker_base.py +432 -0
- tradingapi-0.1.2/tradingapi/config.py +172 -0
- tradingapi-0.1.2/tradingapi/fivepaisa.py +1276 -0
- tradingapi-0.1.2/tradingapi/shoonya.py +1282 -0
- tradingapi-0.1.2/tradingapi/testing.py +48 -0
- tradingapi-0.1.2/tradingapi/utils.py +2251 -0
- tradingapi-0.1.2/tradingapi.egg-info/PKG-INFO +14 -0
- tradingapi-0.1.2/tradingapi.egg-info/SOURCES.txt +13 -0
- tradingapi-0.1.2/tradingapi.egg-info/dependency_links.txt +1 -0
- tradingapi-0.1.2/tradingapi.egg-info/requires.txt +5 -0
- tradingapi-0.1.2/tradingapi.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tradingapi
|
|
3
|
+
Version: 0.1.2
|
|
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.19
|
|
13
|
+
Requires-Dist: redis==5.0.4
|
|
14
|
+
Requires-Dist: pyotp==2.9.0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[tool.black]
|
|
2
|
+
line-length = 88
|
|
3
|
+
|
|
4
|
+
[tool.flake8]
|
|
5
|
+
max-line-length = 88
|
|
6
|
+
|
|
7
|
+
[tool.mypy]
|
|
8
|
+
ignore_missing_imports = true
|
|
9
|
+
|
|
10
|
+
[[tool.mypy.overrides]]
|
|
11
|
+
module = ["yaml", "pytz","requests"]
|
|
12
|
+
ignore_missing_imports = true
|
|
13
|
+
|
|
14
|
+
[[tool.mypy.overrides]]
|
|
15
|
+
module = ["numpy", "pyreadr", "rpy2", "pandas", "scipy","redis"]
|
|
16
|
+
ignore_missing_imports = true
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["setuptools>=42", "wheel"]
|
|
20
|
+
build-backend = "setuptools.build_meta"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools]
|
|
23
|
+
include-package-data = true
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.package-data]
|
|
26
|
+
"tradingapi2" = ["config/config_sample.yaml", "config/commissions_20241216.yaml"]
|
|
27
|
+
|
|
28
|
+
[project]
|
|
29
|
+
name = "tradingapi"
|
|
30
|
+
version = "0.1.2"
|
|
31
|
+
description = "Trade integration with brokers"
|
|
32
|
+
readme = "README.md"
|
|
33
|
+
license = "MIT"
|
|
34
|
+
authors = [
|
|
35
|
+
{ name = "Pankaj Sharma", email = "sharma.pankaj.kumar@gmail.com" }
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"chameli",
|
|
39
|
+
"NorenRestApi==0.0.32",
|
|
40
|
+
"py5paisa==0.7.19",
|
|
41
|
+
"redis==5.0.4",
|
|
42
|
+
"pyotp==2.9.0"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
homepage = "https://bitbucket.org/incurrency/tradingpapi2"
|
|
47
|
+
repository = "https://bitbucket.org/incurrency/tradingapi2"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# tradingAPI/__init__.py
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from importlib.resources import files
|
|
6
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
7
|
+
|
|
8
|
+
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
|
9
|
+
from .config import is_config_loaded, load_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_default_config_path():
|
|
13
|
+
"""Returns the path to the default config file included in the package."""
|
|
14
|
+
return files("tradingapi").joinpath("config/config.yaml")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def configure_logging(
|
|
18
|
+
module_names=None,
|
|
19
|
+
level=logging.WARNING,
|
|
20
|
+
log_file=None,
|
|
21
|
+
clear_existing_handlers=False,
|
|
22
|
+
enable_console=True,
|
|
23
|
+
backup_count=7,
|
|
24
|
+
format_string="%(asctime)s:%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s",
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Configure logging for specific modules or all modules in the tradingAPI package.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
module_names (list of str): List of module names to enable logging for. If None, configure logging for all modules.
|
|
31
|
+
level (int): Logging level (e.g., logging.DEBUG, logging.INFO).
|
|
32
|
+
log_file (str): Path to the log file. If None, logs will go to the console.
|
|
33
|
+
clear_existing_handlers (bool): Whether to clear existing handlers from the root logger.
|
|
34
|
+
enable_console (bool): Whether console logging is enabled
|
|
35
|
+
backup_count (int): number of log files to keep
|
|
36
|
+
"""
|
|
37
|
+
if clear_existing_handlers:
|
|
38
|
+
for handler in logging.root.handlers[:]:
|
|
39
|
+
logging.root.removeHandler(handler)
|
|
40
|
+
|
|
41
|
+
# Create handlers
|
|
42
|
+
handlers = []
|
|
43
|
+
formatter = logging.Formatter(format_string)
|
|
44
|
+
if log_file:
|
|
45
|
+
file_handler = TimedRotatingFileHandler(log_file, when="midnight", backupCount=backup_count)
|
|
46
|
+
file_handler.suffix = "%Y%m%d"
|
|
47
|
+
file_handler.setLevel(level)
|
|
48
|
+
file_handler.setFormatter(formatter)
|
|
49
|
+
handlers.append(file_handler)
|
|
50
|
+
|
|
51
|
+
if enable_console:
|
|
52
|
+
console_handler = logging.StreamHandler()
|
|
53
|
+
console_handler.setLevel(level)
|
|
54
|
+
console_handler.setFormatter(formatter)
|
|
55
|
+
handlers.append(console_handler)
|
|
56
|
+
|
|
57
|
+
# Configure logging for specific modules or globally
|
|
58
|
+
if module_names:
|
|
59
|
+
for module_name in module_names:
|
|
60
|
+
logger = logging.getLogger(module_name)
|
|
61
|
+
logger.setLevel(level)
|
|
62
|
+
for handler in handlers:
|
|
63
|
+
logger.addHandler(handler)
|
|
64
|
+
else:
|
|
65
|
+
# Configure root logger if no specific modules are mentioned
|
|
66
|
+
root_logger = logging.getLogger()
|
|
67
|
+
root_logger.setLevel(level)
|
|
68
|
+
for handler in handlers:
|
|
69
|
+
root_logger.addHandler(handler)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# # Set default logging level to WARNING and log to console by default
|
|
73
|
+
configure_logging()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def initialize_config(config_file_path: str, force_reload=True):
|
|
77
|
+
if is_config_loaded() and not force_reload:
|
|
78
|
+
raise RuntimeError("Configuration is already loaded.")
|
|
79
|
+
else:
|
|
80
|
+
load_config(config_file_path)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
initialize_config(get_default_config_path())
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import json
|
|
3
|
+
import math
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Dict, List, Union
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import redis
|
|
11
|
+
|
|
12
|
+
NEXT_DAY_TIMESTAMP = int((dt.datetime.today() + dt.timedelta(days=1)).timestamp())
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Brokers(Enum):
|
|
16
|
+
UNDEFINED = 1
|
|
17
|
+
FIVEPAISA = 2
|
|
18
|
+
SHOONYA = 3
|
|
19
|
+
INTERACTIVEBROKERS = 4
|
|
20
|
+
DHAN = 5
|
|
21
|
+
ICICIDIRECT = 6
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class HistoricalData:
|
|
26
|
+
date: dt.datetime
|
|
27
|
+
open: float
|
|
28
|
+
high: float
|
|
29
|
+
low: float
|
|
30
|
+
close: float
|
|
31
|
+
volume: int
|
|
32
|
+
intoi: int
|
|
33
|
+
oi: int
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> dict:
|
|
36
|
+
return {k: v for k, v in asdict(self).items() if v is not None and not (isinstance(v, float) and math.isnan(v))}
|
|
37
|
+
# return asdict(self)
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f"HistoricalData({self.__dict__})"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OrderStatus(Enum):
|
|
44
|
+
UNDEFINED = 1 # No information on status
|
|
45
|
+
HISTORICAL = 2 # Order is from earlier days, no broker status
|
|
46
|
+
PENDING = 3 # Pending with Broker
|
|
47
|
+
REJECTED = 4 # Rejected by Broker
|
|
48
|
+
OPEN = 5 # active with exchange
|
|
49
|
+
FILLED = 6 # Filled with exchange
|
|
50
|
+
CANCELLED = 7 # Cancelled by Exchange
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Order:
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
long_symbol: str = "",
|
|
57
|
+
order_type: str = "",
|
|
58
|
+
price_type: float = 0.0,
|
|
59
|
+
quantity: int = 0,
|
|
60
|
+
exchange: str = "",
|
|
61
|
+
exchange_segment: str = "",
|
|
62
|
+
price: float = float("nan"),
|
|
63
|
+
is_intraday: bool = True,
|
|
64
|
+
internal_order_id: str = "",
|
|
65
|
+
remote_order_id: str = "",
|
|
66
|
+
scrip_code: int = 0,
|
|
67
|
+
exch_order_id: str = "0",
|
|
68
|
+
broker_order_id: str = "0",
|
|
69
|
+
stoploss_price: float = 0.0,
|
|
70
|
+
is_stoploss_order: bool = False,
|
|
71
|
+
ioc_order: bool = False,
|
|
72
|
+
scripdata: str = "",
|
|
73
|
+
orderRef: str = "",
|
|
74
|
+
order_id: int = 0,
|
|
75
|
+
local_order_id: int = 0,
|
|
76
|
+
disqty: int = 0,
|
|
77
|
+
message: str = "",
|
|
78
|
+
status: str = "UNDEFINED",
|
|
79
|
+
vtd: str = f"/Date({NEXT_DAY_TIMESTAMP})/",
|
|
80
|
+
ahplaced: str = "N",
|
|
81
|
+
IsGTCOrder: bool = False,
|
|
82
|
+
IsEOSOrder: bool = False,
|
|
83
|
+
paper: bool = True,
|
|
84
|
+
broker: str = "UNDEFINED",
|
|
85
|
+
additional_info: str = "",
|
|
86
|
+
**kwargs: Any,
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
Initialize an Order object with various attributes related to trading orders.
|
|
90
|
+
"""
|
|
91
|
+
self.long_symbol = long_symbol
|
|
92
|
+
self.price_type = price_type
|
|
93
|
+
self.order_type = order_type
|
|
94
|
+
self.quantity = self._convert_to_int(quantity)
|
|
95
|
+
self.price = self._convert_to_float(price)
|
|
96
|
+
self.exchange = exchange
|
|
97
|
+
self.exchange_segment = exchange_segment
|
|
98
|
+
self.internal_order_id = internal_order_id
|
|
99
|
+
self.remote_order_id = remote_order_id
|
|
100
|
+
self.scrip_code = self._convert_to_int(scrip_code)
|
|
101
|
+
self.exch_order_id = exch_order_id
|
|
102
|
+
self.broker_order_id = broker_order_id
|
|
103
|
+
self.stoploss_price = self._convert_to_float(stoploss_price)
|
|
104
|
+
self.is_stoploss_order = self._convert_to_bool(is_stoploss_order)
|
|
105
|
+
self.ioc_order = self._convert_to_bool(ioc_order)
|
|
106
|
+
self.scripdata = scripdata
|
|
107
|
+
self.orderRef = orderRef
|
|
108
|
+
self.order_id = self._convert_to_int(order_id)
|
|
109
|
+
self.local_order_id = self._convert_to_int(local_order_id)
|
|
110
|
+
self.disqty = self._convert_to_int(disqty)
|
|
111
|
+
self.message = message
|
|
112
|
+
self.vtd = vtd
|
|
113
|
+
self.ahplaced = ahplaced
|
|
114
|
+
self.IsGTCOrder = self._convert_to_bool(IsGTCOrder)
|
|
115
|
+
self.IsEOSOrder = self._convert_to_bool(IsEOSOrder)
|
|
116
|
+
self.paper = self._convert_to_bool(paper)
|
|
117
|
+
self.is_intraday = self._convert_to_bool(is_intraday)
|
|
118
|
+
self.additional_info = additional_info
|
|
119
|
+
|
|
120
|
+
# Setting status using enum
|
|
121
|
+
self.status = self._set_status(status)
|
|
122
|
+
|
|
123
|
+
# Setting broker using enum
|
|
124
|
+
self.broker = self._set_broker(broker)
|
|
125
|
+
|
|
126
|
+
# Handling any additional keyword arguments
|
|
127
|
+
for key, value in kwargs.items():
|
|
128
|
+
setattr(self, key, value)
|
|
129
|
+
|
|
130
|
+
def _set_status(self, status: str) -> OrderStatus:
|
|
131
|
+
"""
|
|
132
|
+
Convert a string status to an OrderStatus enum. Default to UNDEFINED if invalid.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
return OrderStatus[status.upper()]
|
|
136
|
+
except KeyError:
|
|
137
|
+
return OrderStatus.UNDEFINED
|
|
138
|
+
|
|
139
|
+
def _set_broker(self, broker: str) -> Brokers:
|
|
140
|
+
"""
|
|
141
|
+
Convert a string broker to a Brokers enum. Default to UNDEFINED if invalid.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
return Brokers[broker.upper()]
|
|
145
|
+
except KeyError:
|
|
146
|
+
return Brokers.UNDEFINED
|
|
147
|
+
|
|
148
|
+
def _convert_to_int(self, value: Any) -> int:
|
|
149
|
+
"""
|
|
150
|
+
Convert a value to an integer if possible, otherwise return None.
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
return int(float(value))
|
|
154
|
+
except (TypeError, ValueError):
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
def _convert_to_float(self, value: Any) -> float:
|
|
158
|
+
"""
|
|
159
|
+
Convert a value to a float if possible, otherwise return NaN.
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
return float(value)
|
|
163
|
+
except (TypeError, ValueError):
|
|
164
|
+
return float("nan")
|
|
165
|
+
|
|
166
|
+
def _convert_to_bool(self, value: Any) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Convert a value to a boolean if possible.
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(value, str):
|
|
171
|
+
return value.lower() in ["true", "1", "yes"]
|
|
172
|
+
return bool(value)
|
|
173
|
+
|
|
174
|
+
def to_dict(self):
|
|
175
|
+
result = self.__dict__.copy()
|
|
176
|
+
# Convert enum to its name (string)
|
|
177
|
+
result["status"] = self.status.name
|
|
178
|
+
result["broker"] = self.broker.name
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
def __repr__(self):
|
|
182
|
+
return json.dumps(self.to_dict(), indent=4, default=str)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Price:
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
bid: float = float("nan"),
|
|
189
|
+
ask: float = float("nan"),
|
|
190
|
+
bid_volume: int = 0,
|
|
191
|
+
ask_volume: int = 0,
|
|
192
|
+
prior_close: float = float("nan"),
|
|
193
|
+
last: float = float("nan"),
|
|
194
|
+
high: float = float("nan"),
|
|
195
|
+
low: float = float("nan"),
|
|
196
|
+
volume: int = 0,
|
|
197
|
+
symbol: str = "",
|
|
198
|
+
exchange: str = "",
|
|
199
|
+
src: str = "",
|
|
200
|
+
timestamp: str = "",
|
|
201
|
+
):
|
|
202
|
+
self.bid = bid
|
|
203
|
+
self.ask = ask
|
|
204
|
+
self.bid_volume = bid_volume
|
|
205
|
+
self.ask_volume = ask_volume
|
|
206
|
+
self.prior_close = prior_close
|
|
207
|
+
self.last = last
|
|
208
|
+
self.high = high
|
|
209
|
+
self.low = low
|
|
210
|
+
self.volume = volume
|
|
211
|
+
self.symbol = symbol
|
|
212
|
+
self.exchange = exchange
|
|
213
|
+
self.src = src
|
|
214
|
+
self.timestamp = timestamp
|
|
215
|
+
|
|
216
|
+
def __add__(self, other):
|
|
217
|
+
def safe_add(a, b):
|
|
218
|
+
if math.isnan(a) or math.isnan(b):
|
|
219
|
+
return float("nan")
|
|
220
|
+
return a + b
|
|
221
|
+
|
|
222
|
+
return Price(
|
|
223
|
+
bid=safe_add(self.bid, other.bid),
|
|
224
|
+
ask=safe_add(self.ask, other.ask),
|
|
225
|
+
bid_volume=safe_add(self.bid_volume, other.bid_volume),
|
|
226
|
+
ask_volume=safe_add(self.ask_volume, other.ask_volume),
|
|
227
|
+
prior_close=safe_add(self.prior_close, other.prior_close),
|
|
228
|
+
last=safe_add(self.last, other.last),
|
|
229
|
+
high=safe_add(self.high, other.high),
|
|
230
|
+
low=safe_add(self.low, other.low),
|
|
231
|
+
volume=safe_add(self.volume, other.volume),
|
|
232
|
+
)
|
|
233
|
+
# dont change symbol
|
|
234
|
+
|
|
235
|
+
def update(self, other, size=1):
|
|
236
|
+
self.bid = other.bid * size if other.bid * size is not float("nan") else self.bid
|
|
237
|
+
self.ask = other.ask * size if other.ask * size is not float("nan") else self.ask
|
|
238
|
+
self.bid_volume = other.bid_volume if other.bid_volume is not float("nan") else self.bid_volume
|
|
239
|
+
self.ask_volume = other.ask_volume if other.ask_volume is not float("nan") else self.ask_volume
|
|
240
|
+
self.prior_close = other.prior_close * size if other.prior_close is not float("nan") else self.prior_close
|
|
241
|
+
self.last = other.last * size if other.last * size is not float("nan") else self.last
|
|
242
|
+
self.high = other.high * size if other.high * size is not float("nan") else self.high
|
|
243
|
+
self.low = other.low * size if other.low * size is not float("nan") else self.low
|
|
244
|
+
self.volume = other.volume if other.volume is not float("nan") else self.volume
|
|
245
|
+
self.symbol = other.symbol
|
|
246
|
+
self.exchange = other.exchange
|
|
247
|
+
self.src = other.src
|
|
248
|
+
self.timestamp = other.timestamp
|
|
249
|
+
|
|
250
|
+
def to_dict(self):
|
|
251
|
+
return {
|
|
252
|
+
"bid": self.bid,
|
|
253
|
+
"ask": self.ask,
|
|
254
|
+
"bid_volume": self.bid_volume,
|
|
255
|
+
"ask_volume": self.ask_volume,
|
|
256
|
+
"prior_close": self.prior_close,
|
|
257
|
+
"last": self.last,
|
|
258
|
+
"high": self.high,
|
|
259
|
+
"low": self.low,
|
|
260
|
+
"volume": self.volume,
|
|
261
|
+
"symbol": self.symbol,
|
|
262
|
+
"exchange": self.exchange,
|
|
263
|
+
"src": self.src,
|
|
264
|
+
"timestamp": self.timestamp,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def from_dict(cls, data):
|
|
269
|
+
return cls(
|
|
270
|
+
bid=data.get("bid", float("nan")),
|
|
271
|
+
ask=data.get("ask", float("nan")),
|
|
272
|
+
bid_volume=data.get("bid_volume", 0),
|
|
273
|
+
ask_volume=data.get("ask_volume", 0),
|
|
274
|
+
prior_close=data.get("prior_close", float("nan")),
|
|
275
|
+
last=data.get("last", float("nan")),
|
|
276
|
+
high=data.get("high", float("nan")),
|
|
277
|
+
low=data.get("low", float("nan")),
|
|
278
|
+
volume=data.get("volume", 0),
|
|
279
|
+
symbol=data.get("symbol", ""),
|
|
280
|
+
exchange=data.get("exchange", ""),
|
|
281
|
+
src=data.get("src", ""),
|
|
282
|
+
timestamp=data.get("timestamp", ""),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def __repr__(self):
|
|
286
|
+
return json.dumps(self.to_dict(), indent=4, default=str)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class Position:
|
|
291
|
+
symbol: str = ""
|
|
292
|
+
size: int = 0
|
|
293
|
+
price: float = 0
|
|
294
|
+
value: float = 0
|
|
295
|
+
|
|
296
|
+
def to_dict(self) -> dict:
|
|
297
|
+
return {k: v for k, v in asdict(self).items() if v is not None and not (isinstance(v, float) and math.isnan(v))}
|
|
298
|
+
# return asdict(self)
|
|
299
|
+
|
|
300
|
+
def __repr__(self):
|
|
301
|
+
return f"Position({self.__dict__})"
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class OrderInfo:
|
|
305
|
+
def __init__(
|
|
306
|
+
self,
|
|
307
|
+
order_size: int = 0,
|
|
308
|
+
order_price: float = float("nan"),
|
|
309
|
+
fill_size: int = 0,
|
|
310
|
+
fill_price: float = 0,
|
|
311
|
+
status: OrderStatus = OrderStatus.UNDEFINED,
|
|
312
|
+
broker_order_id: str = "",
|
|
313
|
+
exchange_order_id: str = "",
|
|
314
|
+
broker=Brokers.UNDEFINED,
|
|
315
|
+
):
|
|
316
|
+
self.order_size = order_size
|
|
317
|
+
self.order_price = order_price
|
|
318
|
+
self.fill_size = fill_size
|
|
319
|
+
self.fill_price = fill_price
|
|
320
|
+
self.status = status
|
|
321
|
+
self.broker_order_id = broker_order_id
|
|
322
|
+
self.exchange_order_id = exchange_order_id
|
|
323
|
+
self.broker = broker
|
|
324
|
+
|
|
325
|
+
def to_dict(self):
|
|
326
|
+
return {
|
|
327
|
+
"order_size": str(self.order_size),
|
|
328
|
+
"order_price": str(self.order_price),
|
|
329
|
+
"fill_size": str(self.fill_size),
|
|
330
|
+
"fill_price": str(self.fill_price),
|
|
331
|
+
"status": self.status.name if isinstance(self.status, Enum) else self.status,
|
|
332
|
+
"broker_order_id": self.broker_order_id,
|
|
333
|
+
"exchange_order_id": self.exchange_order_id,
|
|
334
|
+
"broker": self.broker.name if isinstance(self.broker, Enum) else self.broker,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
def __repr__(self):
|
|
338
|
+
return json.dumps(self.to_dict(), indent=4, default=str)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class BrokerBase(ABC):
|
|
342
|
+
@abstractmethod
|
|
343
|
+
def __init__(self, **kwargs):
|
|
344
|
+
self.broker = Brokers.UNDEFINED
|
|
345
|
+
self.starting_order_ids_int = {}
|
|
346
|
+
self.redis_o = redis.Redis(db=0, charset="utf-8", decode_responses=True)
|
|
347
|
+
self.exchange_mappings = {
|
|
348
|
+
"symbol_map": {},
|
|
349
|
+
"contractsize_map": {},
|
|
350
|
+
"exchange_map": {},
|
|
351
|
+
"exchangetype_map": {},
|
|
352
|
+
"contracttick_map": {},
|
|
353
|
+
"symbol_map_reversed": {},
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@abstractmethod
|
|
357
|
+
def update_symbology(self, **kwargs):
|
|
358
|
+
pass
|
|
359
|
+
|
|
360
|
+
@abstractmethod
|
|
361
|
+
def connect(self, redis_db: int):
|
|
362
|
+
pass
|
|
363
|
+
|
|
364
|
+
@abstractmethod
|
|
365
|
+
def is_connected(self):
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
@abstractmethod
|
|
369
|
+
def disconnect(self):
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
@abstractmethod
|
|
373
|
+
def place_order(self, order: Order, **kwargs) -> Order:
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
@abstractmethod
|
|
377
|
+
def modify_order(self, **kwargs) -> Order:
|
|
378
|
+
pass
|
|
379
|
+
|
|
380
|
+
@abstractmethod
|
|
381
|
+
def cancel_order(self, **kwargs) -> Order:
|
|
382
|
+
pass
|
|
383
|
+
|
|
384
|
+
@abstractmethod
|
|
385
|
+
def get_order_info(self, **kwargs) -> OrderInfo:
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
@abstractmethod
|
|
389
|
+
def get_historical(
|
|
390
|
+
self,
|
|
391
|
+
symbols: Union[str, pd.DataFrame, dict],
|
|
392
|
+
date_start: str,
|
|
393
|
+
date_end: str = dt.datetime.today().strftime("%Y-%m-%d"),
|
|
394
|
+
exchange: str = "N",
|
|
395
|
+
periodicity: str = "1m",
|
|
396
|
+
market_close_time: str = "15:30:00",
|
|
397
|
+
) -> Dict[str, List[HistoricalData]]:
|
|
398
|
+
pass
|
|
399
|
+
|
|
400
|
+
@abstractmethod
|
|
401
|
+
def map_exchange_for_api(self, long_symbol, exchange) -> str:
|
|
402
|
+
"""maps exchange to exchange code needed by broker API."""
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
@abstractmethod
|
|
406
|
+
def map_exchange_for_db(self, long_symbol, exchange) -> str:
|
|
407
|
+
"""maps exchange to NSE or BSE or MCX. The long form exchange name."""
|
|
408
|
+
pass
|
|
409
|
+
|
|
410
|
+
@abstractmethod
|
|
411
|
+
def get_quote(self, long_symbol: str, exchange="NSE") -> Price:
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
@abstractmethod
|
|
415
|
+
def get_position(self, long_symbol: str) -> Union[pd.DataFrame, int]:
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
@abstractmethod
|
|
419
|
+
def get_orders_today(self, **kwargs) -> pd.DataFrame:
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
@abstractmethod
|
|
423
|
+
def get_trades_today(self, **kwargs) -> pd.DataFrame:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
@abstractmethod
|
|
427
|
+
def get_long_name_from_broker_identifier(self, **kwargs) -> pd.Series:
|
|
428
|
+
pass
|
|
429
|
+
|
|
430
|
+
@abstractmethod
|
|
431
|
+
def get_min_lot_size(self, long_symbol: str, exchange: str) -> int:
|
|
432
|
+
pass
|