quant-agent 0.3.0__py3-none-any.whl
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.
- quant_agent/__init__.py +25 -0
- quant_agent/broker/__init__.py +20 -0
- quant_agent/broker/dnse.py +257 -0
- quant_agent/chart.py +235 -0
- quant_agent/config.py +60 -0
- quant_agent/economics.py +1 -0
- quant_agent/fundamental.py +580 -0
- quant_agent/funds.py +270 -0
- quant_agent/integration.py +54 -0
- quant_agent/sources/__init__.py +34 -0
- quant_agent/sources/dnse.py +18 -0
- quant_agent/sources/fmarket.py +18 -0
- quant_agent/sources/http_client.py +52 -0
- quant_agent/sources/ssi.py +27 -0
- quant_agent/sources/vci.py +28 -0
- quant_agent/sources/vps.py +18 -0
- quant_agent/technical.py +134 -0
- quant_agent/trading.py +110 -0
- quant_agent/utils.py +53 -0
- quant_agent-0.3.0.dist-info/METADATA +450 -0
- quant_agent-0.3.0.dist-info/RECORD +23 -0
- quant_agent-0.3.0.dist-info/WHEEL +5 -0
- quant_agent-0.3.0.dist-info/top_level.txt +1 -0
quant_agent/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
quant_agent (quant-agent): Vietnamese stock market data, read-only, no account/API key needed.
|
|
3
|
+
|
|
4
|
+
Every function below is exposed at the top level (`quant_agent.stock_historical_data(...)`,
|
|
5
|
+
etc.) via the re-exports below, so submodule names (technical, fundamental, ...) are
|
|
6
|
+
an internal organizing detail, not part of the public API contract.
|
|
7
|
+
|
|
8
|
+
The one exception is the `broker` subpackage (e.g. `DNSEClient`): it performs
|
|
9
|
+
real authenticated trading (login, place_order, cancel_order) against a real
|
|
10
|
+
brokerage account, a fundamentally different risk profile from the rest of
|
|
11
|
+
this read-only library. It's re-exported here too for backward
|
|
12
|
+
compatibility, but see `broker/__init__.py` before using it.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__author__ = "quant-agent"
|
|
16
|
+
|
|
17
|
+
from .config import * # date helpers + re-exports the `sources` subpackage
|
|
18
|
+
from .utils import *
|
|
19
|
+
from .fundamental import * # stock listing, company profile, financials
|
|
20
|
+
from .technical import * # historical/intraday OHLC price data
|
|
21
|
+
from .trading import * # price board, price depth, intraday trades
|
|
22
|
+
from .funds import * # mutual fund data
|
|
23
|
+
from .chart import * # candlestick / Bollinger Bands visualization
|
|
24
|
+
from .integration import * # third-party data export (Amibroker)
|
|
25
|
+
from .broker import * # authenticated brokerage trading clients - see broker/__init__.py
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authenticated brokerage trading clients - login, place/cancel real orders,
|
|
3
|
+
check balances. A DIFFERENT RISK CATEGORY from every other package in
|
|
4
|
+
quant_agent, which is read-only market data requiring no account or credentials.
|
|
5
|
+
Nothing here is called automatically; each client must be constructed and
|
|
6
|
+
logged in explicitly.
|
|
7
|
+
|
|
8
|
+
To add a new broker: create `broker/<name>.py` with its own client class
|
|
9
|
+
(see `dnse.py` for the shape: `login`, `place_order`, `cancel_order`,
|
|
10
|
+
account/balance getters), then add one line below (both the
|
|
11
|
+
`from .<name> import *` AND its client class name in `__all__` - see
|
|
12
|
+
`sources/__init__.py` for why `__all__` matters here). There's no shared
|
|
13
|
+
base class yet since only one broker is implemented so far - once a second
|
|
14
|
+
one lands, factor out whatever actually turns out to be common (e.g. the
|
|
15
|
+
Bearer-token-per-request pattern) rather than guessing at an interface now.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .dnse import *
|
|
19
|
+
|
|
20
|
+
__all__ = ['DNSEClient']
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# DNSE Lightspeed API: https://www.dnse.vn
|
|
2
|
+
"""
|
|
3
|
+
!!! DIFFERENT RISK CATEGORY FROM THE REST OF THIS LIBRARY !!!
|
|
4
|
+
|
|
5
|
+
Every other module in quant_agent is read-only market data (prices, financials,
|
|
6
|
+
listings). `DNSEClient` below is NOT: it authenticates with real DNSE
|
|
7
|
+
brokerage credentials and can PLACE and CANCEL REAL ORDERS
|
|
8
|
+
(`place_order`, `cancel_order`) against a real trading account with real
|
|
9
|
+
money.
|
|
10
|
+
|
|
11
|
+
Nothing in this file is called automatically by any other part of quant_agent -
|
|
12
|
+
you must construct a `DNSEClient`, call `.login(...)`, and invoke its
|
|
13
|
+
methods explicitly to do anything with it.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from ..config import *
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DNSEClient:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.token = None
|
|
22
|
+
self.trading_token = None
|
|
23
|
+
|
|
24
|
+
def login(self, user_name, password):
|
|
25
|
+
"""
|
|
26
|
+
Authenticate the user and obtain a JWT token for further API requests.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
user_name (str): DNSE username. Can be 064CXXXXX, your email, or your phone number.
|
|
30
|
+
password (str): Your DNSE password.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
str: JWT token if authentication is successful, None otherwise.
|
|
34
|
+
"""
|
|
35
|
+
url = "https://services.entrade.com.vn/dnse-user-service/api/auth"
|
|
36
|
+
payload = json.dumps({'username': user_name, 'password': password})
|
|
37
|
+
headers = {'Content-Type': 'application/json'}
|
|
38
|
+
data = fetch_json('POST', url, headers=headers, data=payload, source='DNSE')
|
|
39
|
+
if data is None:
|
|
40
|
+
print('Login failed')
|
|
41
|
+
return None
|
|
42
|
+
print('Login successfully')
|
|
43
|
+
self.token = data['token']
|
|
44
|
+
return self.token
|
|
45
|
+
|
|
46
|
+
def account(self):
|
|
47
|
+
"""
|
|
48
|
+
Get the full user profile from DNSE.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
DataFrame: A DataFrame containing the user profile if successful, None otherwise.
|
|
52
|
+
"""
|
|
53
|
+
url = "https://services.entrade.com.vn/dnse-user-service/api/me"
|
|
54
|
+
headers = {'Content-Type': 'text/plain', 'Authorization': f'Bearer {self.token}'}
|
|
55
|
+
data = fetch_json('GET', url, headers=headers, data="N/A", source='DNSE')
|
|
56
|
+
if data is None:
|
|
57
|
+
print('Get profile failed')
|
|
58
|
+
return None
|
|
59
|
+
print('Get profile successfully')
|
|
60
|
+
return pd.DataFrame(data, index=[0])
|
|
61
|
+
|
|
62
|
+
def sub_accounts(self):
|
|
63
|
+
"""
|
|
64
|
+
Get sub-accounts information.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
DataFrame: A DataFrame containing the sub-accounts information if successful, None otherwise.
|
|
68
|
+
"""
|
|
69
|
+
url = "https://services.entrade.com.vn/dnse-order-service/accounts"
|
|
70
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
71
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
72
|
+
return json_normalize(data['accounts']) if data is not None else None
|
|
73
|
+
|
|
74
|
+
def account_balance (self, sub_account):
|
|
75
|
+
"""
|
|
76
|
+
Get account balance for a specific sub-account.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
80
|
+
"""
|
|
81
|
+
url = f"https://services.entrade.com.vn/dnse-order-service/account-balances/{sub_account}"
|
|
82
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
83
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
84
|
+
return json_normalize(data) if data is not None else None
|
|
85
|
+
|
|
86
|
+
def email_otp(self):
|
|
87
|
+
"""
|
|
88
|
+
Trigger an email OTP request to DNSE. The OTP will be sent to the email address associated with the DNSE account.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
None.
|
|
92
|
+
"""
|
|
93
|
+
url = "https://services.entrade.com.vn/dnse-auth-service/api/email-otp"
|
|
94
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
95
|
+
response = fetch('GET', url, headers=headers, source='DNSE')
|
|
96
|
+
if response is not None:
|
|
97
|
+
print("OTP sent to email")
|
|
98
|
+
|
|
99
|
+
def get_trading_token(self, otp, smart_otp=True):
|
|
100
|
+
"""
|
|
101
|
+
Authenticate using OTP and get the trading token.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
otp (str): OTP code for authentication. Input as a string.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
str: Trading token if authentication is successful, None otherwise.
|
|
108
|
+
"""
|
|
109
|
+
if smart_otp:
|
|
110
|
+
url = "https://services.entrade.com.vn/dnse-order-service/trading-token"
|
|
111
|
+
headers = {"Authorization": f"Bearer {self.token}", "smart-otp": otp}
|
|
112
|
+
else:
|
|
113
|
+
url = "https://services.entrade.com.vn/dnse-auth-service/api/email-otp"
|
|
114
|
+
headers = {"Authorization": f"Bearer {self.token}", "otp": otp}
|
|
115
|
+
data = fetch_json('POST', url, headers=headers, source='DNSE')
|
|
116
|
+
if data is None:
|
|
117
|
+
print("Error authenticating")
|
|
118
|
+
return None
|
|
119
|
+
self.trading_token = data.get("tradingToken")
|
|
120
|
+
print("Authenticated! Trading token returned.")
|
|
121
|
+
return self.trading_token
|
|
122
|
+
|
|
123
|
+
def loan_packages(self, sub_account, asset_type='stock'):
|
|
124
|
+
"""
|
|
125
|
+
Get the list of loan packages for a specific sub account.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
129
|
+
asset_type (str): Asset type, either 'stock' or 'derivative'.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
DataFrame: A DataFrame containing the list of loan packages if successful, None otherwise.
|
|
133
|
+
"""
|
|
134
|
+
suffix = 'loan-packages' if asset_type == 'stock' else 'derivative-loan-packages'
|
|
135
|
+
url = f"https://services.entrade.com.vn/dnse-order-service/accounts/{sub_account}/{suffix}"
|
|
136
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
137
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
138
|
+
return json_normalize(data['loanPackages']) if data is not None else None
|
|
139
|
+
|
|
140
|
+
def trade_capacities(self, symbol, price, sub_account, asset_type='stock', loan_package_id=None):
|
|
141
|
+
"""
|
|
142
|
+
Get the list of trade capacities (sức mua/bán) for a specific sub account.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
symbol (str): Symbol of the asset.
|
|
146
|
+
price (float): Price of the asset, unit is VND.
|
|
147
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
148
|
+
asset_type (str): Asset type, either 'stock' or 'derivative'.
|
|
149
|
+
loan_package_id (int): Loan package ID (if applicable).
|
|
150
|
+
Returns:
|
|
151
|
+
DataFrame: A DataFrame containing the list of trade capacities if successful, None otherwise.
|
|
152
|
+
"""
|
|
153
|
+
if asset_type == 'stock':
|
|
154
|
+
url = f'https://services.entrade.com.vn/dnse-order-service/accounts/{sub_account}/ppse'
|
|
155
|
+
query_params = {"symbol": symbol, "price": price, "loanPackageId": loan_package_id}
|
|
156
|
+
query_params = {k: v for k, v in query_params.items() if v is not None}
|
|
157
|
+
if query_params:
|
|
158
|
+
url += "?" + "&".join(f"{key}={value}" for key, value in query_params.items())
|
|
159
|
+
else:
|
|
160
|
+
url = f'https://services.entrade.com.vn/dnse-order-service/accounts/{sub_account}/derivative-ppse?symbol={symbol}&price={price}&loanPackageId={loan_package_id}'
|
|
161
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
162
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
163
|
+
return json_normalize(data) if data is not None else None
|
|
164
|
+
|
|
165
|
+
def place_order(self, sub_account, symbol, side, quantity, price, order_type, loan_package_id, asset_type='stock'):
|
|
166
|
+
"""
|
|
167
|
+
Place an order for stocks or derivatives. THIS SENDS A REAL ORDER TO A REAL
|
|
168
|
+
BROKERAGE ACCOUNT - it is not a simulation.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
sub_account (str): Sub account number.
|
|
172
|
+
symbol (str): Symbol of the asset.
|
|
173
|
+
side (str): 'buy' or 'sell'.
|
|
174
|
+
quantity (int): Order quantity.
|
|
175
|
+
price (float): Order price.
|
|
176
|
+
loan_package_id (int): Loan package ID (if applicable).
|
|
177
|
+
asset_type (str): Asset type, either 'stock' or 'derivative'.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
DataFrame: A DataFrame containing the order information if successful, None otherwise.
|
|
181
|
+
"""
|
|
182
|
+
side_code = 'NB' if side == 'buy' else 'NS'
|
|
183
|
+
url = ("https://services.entrade.com.vn/dnse-order-service/v2/orders" if asset_type == 'stock'
|
|
184
|
+
else "https://services.entrade.com.vn/dnse-order-service/derivative/orders")
|
|
185
|
+
headers = {"Authorization": f"Bearer {self.token}", "Trading-Token": self.trading_token}
|
|
186
|
+
payload = {
|
|
187
|
+
"accountNo": sub_account, "symbol": symbol, "side": side_code, "quantity": quantity,
|
|
188
|
+
"price": price, 'orderType': order_type, "loanPackageId": loan_package_id,
|
|
189
|
+
}
|
|
190
|
+
data = fetch_json('POST', url, headers=headers, json=payload, source='DNSE')
|
|
191
|
+
return json_normalize(data) if data is not None else None
|
|
192
|
+
|
|
193
|
+
def order_list(self, sub_account, asset_type='stock'):
|
|
194
|
+
"""
|
|
195
|
+
Get the list of orders for a specific account.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
199
|
+
asset_type (str): Asset type, either 'stock' or 'derivative'.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
DataFrame: A DataFrame containing the list of orders if successful, None otherwise.
|
|
203
|
+
"""
|
|
204
|
+
suffix = 'v2/orders' if asset_type == 'stock' else 'derivative/orders'
|
|
205
|
+
url = f"https://services.entrade.com.vn/dnse-order-service/{suffix}?accountNo={sub_account}"
|
|
206
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
207
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
208
|
+
if data is None:
|
|
209
|
+
return None
|
|
210
|
+
print('Order list retrieved')
|
|
211
|
+
return json_normalize(data['orders'])
|
|
212
|
+
|
|
213
|
+
def order_detail(self, order_id, sub_account, asset_type='stock'):
|
|
214
|
+
"""
|
|
215
|
+
Get the details of a specific order for a specific sub account.
|
|
216
|
+
Args:
|
|
217
|
+
order_id (str): Order ID.
|
|
218
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
219
|
+
asset_type (str): Asset type, either 'stock' or 'derivative'.
|
|
220
|
+
"""
|
|
221
|
+
suffix = 'v2/orders' if asset_type == 'stock' else 'derivative/orders'
|
|
222
|
+
url = f"https://services.entrade.com.vn/dnse-order-service/{suffix}/{order_id}?accountNo={sub_account}"
|
|
223
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
224
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
225
|
+
return json_normalize(data) if data is not None else None
|
|
226
|
+
|
|
227
|
+
def cancel_order (self, order_id, sub_account, asset_type='stock'):
|
|
228
|
+
"""
|
|
229
|
+
Cancel an order. THIS AFFECTS A REAL ORDER ON A REAL BROKERAGE ACCOUNT.
|
|
230
|
+
Args:
|
|
231
|
+
order_id (str): Order ID.
|
|
232
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
233
|
+
asset_type (str): Asset type, either 'stock' or 'derivative'.
|
|
234
|
+
"""
|
|
235
|
+
suffix = 'v2/orders' if asset_type == 'stock' else 'derivative/orders'
|
|
236
|
+
url = f"https://services.entrade.com.vn/dnse-order-service/{suffix}/{order_id}?accountNo={sub_account}"
|
|
237
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
238
|
+
response = fetch('DELETE', url, headers=headers, source='DNSE')
|
|
239
|
+
if response is None:
|
|
240
|
+
return None
|
|
241
|
+
print("Order cancelled")
|
|
242
|
+
return json_normalize(response.json())
|
|
243
|
+
|
|
244
|
+
def deals_list (self, sub_account, asset_type='stock'):
|
|
245
|
+
"""
|
|
246
|
+
Get the list of deals for a specific sub account.
|
|
247
|
+
Args:
|
|
248
|
+
sub_account (str): DNSE sub account number (mã tiểu khoản).
|
|
249
|
+
"""
|
|
250
|
+
url = (f'https://services.entrade.com.vn/dnse-deal-service/deals?accountNo={sub_account}' if asset_type == 'stock'
|
|
251
|
+
else f'https://services.entrade.com.vn/dnse-derivative-core/deals?accountNo={sub_account}')
|
|
252
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
253
|
+
data = fetch_json('GET', url, headers=headers, source='DNSE')
|
|
254
|
+
if data is None:
|
|
255
|
+
return None
|
|
256
|
+
print('Deals list retrieved')
|
|
257
|
+
return json_normalize(data['data'])
|
quant_agent/chart.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plotly visualizations built on top of the OHLC DataFrame shape returned by
|
|
3
|
+
`technical.stock_historical_data()` (columns: time, open, high, low, close,
|
|
4
|
+
volume, ticker). Pure computation/plotting - no network calls, so nothing
|
|
5
|
+
here is affected by any data source's availability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .config import *
|
|
9
|
+
from .technical import *
|
|
10
|
+
|
|
11
|
+
import plotly.graph_objs as go
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# CANDLESTICK CHART
|
|
15
|
+
def candlestick_chart(df, title='Candlestick Chart with MA and Volume', x_label='Date', y_label='Price', ma_periods=None, show_volume=True, figure_size=(15, 8), reference_period=None, colors=('#00F4B0', '#FF3747'), reference_colors=('blue', 'black')):
|
|
16
|
+
"""
|
|
17
|
+
Generate a candlestick chart with optional Moving Averages (MA) lines, volume data, and reference lines.
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
- df: DataFrame with candlestick data ('time', 'open', 'high', 'low', 'close', 'volume', 'ticker').
|
|
21
|
+
- title: Title of the chart.
|
|
22
|
+
- x_label: Label for the x-axis.
|
|
23
|
+
- y_label: Label for the y-axis.
|
|
24
|
+
- ma_periods: List of MA periods to calculate and plot (e.g., [10, 50, 200]).
|
|
25
|
+
- show_volume: Boolean to indicate whether to display volume data.
|
|
26
|
+
- figure_size: Tuple specifying the figure size (width, height).
|
|
27
|
+
- reference_period: Number of days to consider for reference lines (e.g., 90).
|
|
28
|
+
- colors: Tuple of color codes for up and down candles (e.g., ('#00F4B0', '#FF3747')).
|
|
29
|
+
- reference_colors: Tuple of color codes for reference lines (e.g., ('black', 'blue')).
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
- Plotly figure object.
|
|
33
|
+
"""
|
|
34
|
+
# Create the base candlestick chart
|
|
35
|
+
candlestick_trace = go.Candlestick(
|
|
36
|
+
x=df['time'],
|
|
37
|
+
open=df['open'],
|
|
38
|
+
high=df['high'],
|
|
39
|
+
low=df['low'],
|
|
40
|
+
close=df['close'],
|
|
41
|
+
name='Candlestick',
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Create a figure
|
|
45
|
+
fig = go.Figure(data=[candlestick_trace])
|
|
46
|
+
|
|
47
|
+
# Add volume data if specified
|
|
48
|
+
if show_volume:
|
|
49
|
+
volume_trace = go.Bar(
|
|
50
|
+
x=df['time'],
|
|
51
|
+
y=df['volume'],
|
|
52
|
+
name='Volume',
|
|
53
|
+
yaxis='y2', # Use the secondary y-axis for volume
|
|
54
|
+
marker=dict(color=[colors[0] if close >= open else colors[1] for close, open in zip(df['close'], df['open'])]), # Match volume color to candle color
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
fig.add_trace(volume_trace)
|
|
58
|
+
|
|
59
|
+
# Add Moving Averages (MA) lines if specified
|
|
60
|
+
if ma_periods:
|
|
61
|
+
for period in ma_periods:
|
|
62
|
+
ma_name = f'{period}-day MA'
|
|
63
|
+
df[ma_name] = df['close'].rolling(period).mean()
|
|
64
|
+
|
|
65
|
+
ma_trace = go.Scatter(
|
|
66
|
+
x=df['time'],
|
|
67
|
+
y=df[ma_name],
|
|
68
|
+
mode='lines',
|
|
69
|
+
name=ma_name,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
fig.add_trace(ma_trace)
|
|
73
|
+
|
|
74
|
+
# Add straight reference lines for the highest high and lowest low
|
|
75
|
+
if reference_period:
|
|
76
|
+
df['lowest_low'] = df['low'].rolling(reference_period).min()
|
|
77
|
+
df['highest_high'] = df['high'].rolling(reference_period).max()
|
|
78
|
+
|
|
79
|
+
lowest_low_trace = go.Scatter(
|
|
80
|
+
x=df['time'],
|
|
81
|
+
y=[df['lowest_low'].iloc[-1]] * len(df), # Create a straight line for lowest low
|
|
82
|
+
mode='lines',
|
|
83
|
+
name=f'Lowest Low ({reference_period} days)',
|
|
84
|
+
line=dict(color=reference_colors[0], dash='dot'),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
highest_high_trace = go.Scatter(
|
|
88
|
+
x=df['time'],
|
|
89
|
+
y=[df['highest_high'].iloc[-1]] * len(df), # Create a straight line for highest high
|
|
90
|
+
mode='lines',
|
|
91
|
+
name=f'Highest High ({reference_period} days)',
|
|
92
|
+
line=dict(color=reference_colors[1], dash='dot'),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
fig.add_trace(lowest_low_trace)
|
|
96
|
+
fig.add_trace(highest_high_trace)
|
|
97
|
+
|
|
98
|
+
# Customize the chart appearance
|
|
99
|
+
fig.update_layout(
|
|
100
|
+
title=title,
|
|
101
|
+
xaxis_title=x_label,
|
|
102
|
+
yaxis_title=y_label,
|
|
103
|
+
xaxis_rangeslider_visible=True,
|
|
104
|
+
yaxis2=dict(
|
|
105
|
+
title='Volume',
|
|
106
|
+
overlaying='y',
|
|
107
|
+
side='right',
|
|
108
|
+
),
|
|
109
|
+
width=figure_size[0] * 100, # Convert short form to a larger size for better readability
|
|
110
|
+
height=figure_size[1] * 100,
|
|
111
|
+
margin=dict(l=50, r=50, t=70, b=50), # Adjust margins for space between title and legend
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return fig
|
|
115
|
+
|
|
116
|
+
# BOLLINGER BANDS
|
|
117
|
+
|
|
118
|
+
def bollinger_bands(df, window=20, num_std_dev=2):
|
|
119
|
+
"""
|
|
120
|
+
Calculate Bollinger Bands for a DataFrame.
|
|
121
|
+
|
|
122
|
+
Parameters:
|
|
123
|
+
- df: DataFrame with OHLC data ('time', 'open', 'high', 'low', 'close', 'volume', 'ticker').
|
|
124
|
+
- window: The rolling window size for calculating the moving average and standard deviation.
|
|
125
|
+
- num_std_dev: The number of standard deviations to use for the Bollinger Bands.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
- DataFrame with Bollinger Bands ('time', 'upper_band', 'middle_band', 'lower_band').
|
|
129
|
+
"""
|
|
130
|
+
df['middle_band'] = df['close'].rolling(window=window).mean()
|
|
131
|
+
df['rolling_std'] = df['close'].rolling(window=window).std()
|
|
132
|
+
df['upper_band'] = df['middle_band'] + (num_std_dev * df['rolling_std'])
|
|
133
|
+
df['lower_band'] = df['middle_band'] - (num_std_dev * df['rolling_std'])
|
|
134
|
+
df.drop(columns=['rolling_std'], inplace=True)
|
|
135
|
+
return df
|
|
136
|
+
|
|
137
|
+
def bollinger_bands_chart(df, use_candlestick=True, show_volume=True, fig_size=(15, 8), chart_title='Bollinger Bands Chart', xaxis_title='Date', yaxis_title='Price', bollinger_band_colors=('gray', 'orange', 'gray'), volume_colors=('#00F4B0', '#FF3747')):
|
|
138
|
+
"""
|
|
139
|
+
Visualize a candlestick chart or close price chart with Bollinger Bands and volume using Plotly.
|
|
140
|
+
|
|
141
|
+
Parameters:
|
|
142
|
+
- df: DataFrame with Bollinger Bands data ('time', 'open', 'high', 'low', 'close', 'volume', 'ticker', 'upper_band', 'middle_band', 'lower_band').
|
|
143
|
+
- use_candlestick: Boolean to indicate whether to use candlestick chart (default) or close price chart.
|
|
144
|
+
- show_volume: Boolean to indicate whether to display volume data on the main chart.
|
|
145
|
+
- fig_size: Tuple specifying the figure size in short form, e.g., (15, 8) equals to (1500, 800) in actual.
|
|
146
|
+
- chart_title: Title for the chart.
|
|
147
|
+
- xaxis_title: Title for the x-axis.
|
|
148
|
+
- yaxis_title: Title for the y-axis.
|
|
149
|
+
- bollinger_band_colors: Tuple of color codes for the Bollinger Bands (upper, middle, lower).
|
|
150
|
+
- volume_colors: Tuple of color codes for volume bars on up and down days.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
- Plotly figure object.
|
|
154
|
+
"""
|
|
155
|
+
fig = go.Figure()
|
|
156
|
+
|
|
157
|
+
if use_candlestick:
|
|
158
|
+
# Create the candlestick chart
|
|
159
|
+
candlestick_trace = go.Candlestick(
|
|
160
|
+
x=df['time'],
|
|
161
|
+
open=df['open'],
|
|
162
|
+
high=df['high'],
|
|
163
|
+
low=df['low'],
|
|
164
|
+
close=df['close'],
|
|
165
|
+
name='Candlestick',
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
fig.add_trace(candlestick_trace)
|
|
169
|
+
else:
|
|
170
|
+
# Create a chart using close prices
|
|
171
|
+
close_price_trace = go.Scatter(
|
|
172
|
+
x=df['time'],
|
|
173
|
+
y=df['close'],
|
|
174
|
+
mode='lines',
|
|
175
|
+
name='Close Price',
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
fig.add_trace(close_price_trace)
|
|
179
|
+
|
|
180
|
+
# Create the Bollinger Bands traces
|
|
181
|
+
upper_band_trace = go.Scatter(
|
|
182
|
+
x=df['time'],
|
|
183
|
+
y=df['upper_band'],
|
|
184
|
+
mode='lines',
|
|
185
|
+
line=dict(color=bollinger_band_colors[0]),
|
|
186
|
+
name='Upper Bollinger Band',
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
middle_band_trace = go.Scatter(
|
|
190
|
+
x=df['time'],
|
|
191
|
+
y=df['middle_band'],
|
|
192
|
+
mode='lines',
|
|
193
|
+
line=dict(color=bollinger_band_colors[1]),
|
|
194
|
+
name='Middle Bollinger Band',
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
lower_band_trace = go.Scatter(
|
|
198
|
+
x=df['time'],
|
|
199
|
+
y=df['lower_band'],
|
|
200
|
+
mode='lines',
|
|
201
|
+
line=dict(color=bollinger_band_colors[2]),
|
|
202
|
+
name='Lower Bollinger Band',
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
fig.add_trace(upper_band_trace)
|
|
206
|
+
fig.add_trace(middle_band_trace)
|
|
207
|
+
fig.add_trace(lower_band_trace)
|
|
208
|
+
|
|
209
|
+
if show_volume:
|
|
210
|
+
# Create the volume bars with different colors for up and down days
|
|
211
|
+
volume_color = [volume_colors[0] if close >= open else volume_colors[1] for close, open in zip(df['close'], df['open'])]
|
|
212
|
+
|
|
213
|
+
volume_trace = go.Bar(
|
|
214
|
+
x=df['time'],
|
|
215
|
+
y=df['volume'],
|
|
216
|
+
name='Volume',
|
|
217
|
+
marker=dict(color=volume_color),
|
|
218
|
+
yaxis='y2',
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
fig.add_trace(volume_trace)
|
|
222
|
+
|
|
223
|
+
# Customize the chart appearance
|
|
224
|
+
fig.update_layout(
|
|
225
|
+
title=chart_title,
|
|
226
|
+
xaxis_title=xaxis_title,
|
|
227
|
+
yaxis_title=yaxis_title,
|
|
228
|
+
xaxis_rangeslider_visible=True,
|
|
229
|
+
# legend=dict(orientation="h", y=1.05),
|
|
230
|
+
yaxis2=dict(title='Volume', overlaying='y', side='right'),
|
|
231
|
+
width=fig_size[0] * 100, # Convert short form width to full width
|
|
232
|
+
height=fig_size[1] * 100, # Convert short form height to full height
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return fig
|
quant_agent/config.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Date/time helpers used across quant_agent as default values for `start_date`/
|
|
4
|
+
`end_date` parameters, plus a re-export hub: every other module in this
|
|
5
|
+
package does `from .config import *` and expects headers (ssi_headers,
|
|
6
|
+
vci_headers, ...) and HTTP helpers (fetch_json, ...) to be
|
|
7
|
+
available from here. Those actually live in the `sources` subpackage (one
|
|
8
|
+
file per data source, so adding a new source never means editing a shared
|
|
9
|
+
file) - re-exporting them here keeps every module's existing import line
|
|
10
|
+
working unchanged.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import time
|
|
15
|
+
from datetime import datetime, timedelta
|
|
16
|
+
|
|
17
|
+
import pandas as pd
|
|
18
|
+
import requests
|
|
19
|
+
from pandas import json_normalize
|
|
20
|
+
|
|
21
|
+
# Re-exported for backward compatibility - see the `sources` subpackage.
|
|
22
|
+
from .sources import *
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def api_request(url, headers=ssi_headers):
|
|
26
|
+
r = requests.get(url, headers).json()
|
|
27
|
+
return r
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# TRADING INTELLIGENT
|
|
31
|
+
today_val = datetime.now()
|
|
32
|
+
|
|
33
|
+
def today():
|
|
34
|
+
return today_val.strftime('%Y-%m-%d')
|
|
35
|
+
|
|
36
|
+
def last_xd (day_num): # return the date of last x days
|
|
37
|
+
"""
|
|
38
|
+
This function returns the date that X days ago from today in the format of YYYY-MM-DD.
|
|
39
|
+
Args:
|
|
40
|
+
day_num (:obj:`int`, required): numer of days.
|
|
41
|
+
Returns:
|
|
42
|
+
:obj:`str`:
|
|
43
|
+
2022-02-22
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: raised whenever any of the introduced arguments is not valid.
|
|
46
|
+
"""
|
|
47
|
+
return (today_val - timedelta(day_num)).strftime('%Y-%m-%d')
|
|
48
|
+
|
|
49
|
+
def start_xm (period): # return the start date of x months
|
|
50
|
+
"""
|
|
51
|
+
This function returns the start date of X months ago from today in the format of YYYY-MM-DD.
|
|
52
|
+
Args:
|
|
53
|
+
period (:obj:`int`, required): numer of months (period).
|
|
54
|
+
Returns:
|
|
55
|
+
:obj:`str`:
|
|
56
|
+
2022-01-01
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: raised whenever any of the introduced arguments is not valid.
|
|
59
|
+
"""
|
|
60
|
+
return pd.date_range(end=today(), periods=period+1, freq='MS')[0].strftime('%Y-%m-%d')
|
quant_agent/economics.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# To be updated soon
|