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.
@@ -0,0 +1,134 @@
1
+ """
2
+ Historical & intraday OHLC price data.
3
+ - `ohlc_data` / `stock_historical_data(source='DNSE')` -> DNSE/Entrade.
4
+ - `longterm_ohlc_data` / `stock_historical_data(source='VCI')` -> VCI (VietCap Securities).
5
+ """
6
+
7
+ from .config import *
8
+
9
+ def stock_historical_data (symbol='TCB', start_date='2023-06-01', end_date='2023-06-17', resolution='1D', type='stock', beautify=True, decor=False, source='DNSE'):
10
+ """
11
+ Get historical price data. The unit price is VND.
12
+ Parameters:
13
+ symbol (str): ticker of a stock or index. Available indices are: VNINDEX, VN30, HNX, HNX30, UPCOM, VNXALLSHARE, VN30F1M, VN30F2M, VN30F1Q, VN30F2Q
14
+ start_date (str): start date of the historical price data
15
+ end_date (str): end date of the historical price data
16
+ resolution (str): resolution of the historical price data. Default is '1D' (daily), other options are '1' (1 minute), 15 (15 minutes), 30 (30 minutes), '1H' (hourly). For stock, the limit of 90 days is applied to resolution 1, 15, 30, 1H.
17
+ type (str): stock, index, or derivative. Default is 'stock'
18
+ beautify (bool): if True, convert open, high, low, close to VND for stock symbols. Default is True which means the unit price is thousands VND
19
+ decor (bool): if True, rename columns to Title Case (Open, High, Low, Close instead of open, high, low, close) and set Time column as index. Default is False. This option helps integrate quant_agent with other libraries such as TA-Lib out of the box.
20
+ source (str): data source. Default is 'DNSE' (EntradeX); other option is 'VCI' (VietCap Securities) for longterm daily data.
21
+ Returns:
22
+ :obj:`pandas.DataFrame`:
23
+ | time | open | high | low | close | volume |
24
+ | ----------- | ---- | ---- | --- | ----- | ------ |
25
+ | YYYY-mm-dd | xxxx | xxxx | xxx | xxxxx | xxxxxx |
26
+ """
27
+ if source.upper() == 'DNSE':
28
+ df = ohlc_data(symbol, start_date, end_date, resolution, type, headers=entrade_headers)
29
+ elif source.upper() == 'VCI':
30
+ if resolution != '1D':
31
+ print("VCI only supports longterm daily data. Please set resolution to '1D'")
32
+ return None
33
+ df = longterm_ohlc_data(symbol, start_date, end_date, 'D', type)
34
+ else:
35
+ print(f"Unknown source '{source}'. Use 'DNSE' or 'VCI'.")
36
+ return None
37
+ if df is None:
38
+ return None
39
+
40
+ df = df[['time', 'open', 'high', 'low', 'close', 'volume', 'ticker']]
41
+ if beautify and type == 'stock':
42
+ df[['open', 'high', 'low', 'close']] = (df[['open', 'high', 'low', 'close']] * 1000).astype(int)
43
+ if decor:
44
+ df.columns = df.columns.str.title()
45
+ df = df.set_index('Time')
46
+ return df
47
+
48
+
49
+ _VCI_TIMEFRAME_MAP = {'D': 'ONE_DAY', '1D': 'ONE_DAY', 'H': 'ONE_HOUR', '1H': 'ONE_HOUR', '1': 'ONE_MINUTE'}
50
+
51
+ def longterm_ohlc_data (symbol='REE', start_date='2022-01-01', end_date='2023-10-31', resolution='D', type='stock', headers=None):
52
+ """
53
+ Get longterm OHLC data.
54
+ Data source: VCI (VietCap Securities) chart endpoint. Returns up
55
+ to 1000 bars per request in a single call.
56
+ Parameters:
57
+ symbol (str): ticker of a stock or index, default is 'REE'.
58
+ start_date (str): start date, 'YYYY-MM-DD'.
59
+ end_date (str): end date, 'YYYY-MM-DD'.
60
+ resolution (str): 'D' (daily, default), 'H' (hourly), or '1' (1-minute).
61
+ type (str): stock, index, or derivative.
62
+ """
63
+ timeframe = _VCI_TIMEFRAME_MAP.get(resolution, 'ONE_DAY')
64
+ start_dt = datetime.strptime(start_date, '%Y-%m-%d')
65
+ end_dt = datetime.strptime(end_date, '%Y-%m-%d')
66
+ count_back = (end_dt - start_dt).days + 5 # small buffer for non-trading days
67
+ to_timestamp = int(end_dt.timestamp())
68
+
69
+ url = 'https://trading.vietcap.com.vn/api/chart/OHLCChart/gap-chart'
70
+ payload = {'timeFrame': timeframe, 'symbols': [symbol], 'to': to_timestamp, 'countBack': count_back}
71
+ data = fetch_json('POST', url, headers=vci_headers, json=payload, source='VCI')
72
+ if not data:
73
+ print(f'No OHLC data returned by VCI for {symbol}.')
74
+ return None
75
+
76
+ bars = data[0]
77
+ df = pd.DataFrame({
78
+ 'time': pd.to_datetime([int(t) for t in bars['t']], unit='s').strftime('%Y-%m-%d'),
79
+ 'open': bars['o'], 'high': bars['h'], 'low': bars['l'], 'close': bars['c'], 'volume': bars['v'],
80
+ })
81
+ df['ticker'] = symbol
82
+ df = df[(df['time'] >= start_date) & (df['time'] <= end_date)]
83
+ if type == 'stock':
84
+ df[['open', 'high', 'low', 'close']] = round(df[['open', 'high', 'low', 'close']] / 1000, 2)
85
+ df[['open', 'high', 'low', 'close']] = df[['open', 'high', 'low', 'close']].astype(float)
86
+ df['volume'] = df['volume'].astype(int)
87
+ return df.reset_index(drop=True)
88
+
89
+ def ohlc_data (symbol, start_date='2023-06-01', end_date='2023-06-17', resolution='1D', type='stock', headers=entrade_headers):
90
+ """
91
+ Get historical price data from entrade.com.vn. The unit price is VND.
92
+ Parameters:
93
+ symbol (str): ticker of a stock or index. Available indices are: VNINDEX, VN30, HNX, HNX30, UPCOM, VNXALLSHARE, VN30F1M, VN30F2M, VN30F1Q, VN30F2Q
94
+ start_date (str): start date of the historical price data
95
+ end_date (str): end date of the historical price data
96
+ resolution (str): resolution of the historical price data. Default is '1D' (daily), other options are '1' (1 minute), 15 (15 minutes), 30 (30 minutes), '1H' (hourly)
97
+ type (str): stock or index. Default is 'stock'
98
+ headers (dict): headers of the request
99
+ Returns:
100
+ :obj:`pandas.DataFrame`:
101
+ | time | open | high | low | close | volume |
102
+ | ----------- | ---- | ---- | --- | ----- | ------ |
103
+ | YYYY-mm-dd | xxxx | xxxx | xxx | xxxxx | xxxxxx |
104
+ """
105
+ end_date = (datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1)).strftime('%Y-%m-%d')
106
+ start_timestamp = int(datetime.strptime(start_date, '%Y-%m-%d').timestamp())
107
+ end_timestamp = int(datetime.strptime(end_date, '%Y-%m-%d').timestamp())
108
+
109
+ # Resolutions finer than 1 day are limited to the most recent 90 days.
110
+ if resolution != '1D':
111
+ ninety_days_ago = int(datetime.now().timestamp()) - 90 * 24 * 60 * 60
112
+ if end_timestamp < ninety_days_ago:
113
+ print("The 'end_date' value in the report should be no more than 90 days from today for all resolutions shorter than 1 day.\n")
114
+ elif ninety_days_ago > start_timestamp:
115
+ start_timestamp = ninety_days_ago
116
+ print("The retrieval of stock data is restricted to the most recent 90 days from today for all resolutions shorter than 1 day.\n")
117
+
118
+ url = f"https://services.entrade.com.vn/chart-api/v2/ohlcs/{type}?from={start_timestamp}&to={end_timestamp}&symbol={symbol}&resolution={resolution}"
119
+ data = fetch_json('GET', url, headers=headers, source='DNSE')
120
+ if data is None:
121
+ return None
122
+
123
+ df = pd.DataFrame(data)
124
+ df['t'] = pd.to_datetime(df['t'], unit='s')
125
+ df = df.rename(columns={'t': 'time', 'o': 'open', 'h': 'high', 'l': 'low', 'c': 'close', 'v': 'volume'}).drop(columns=['nextTime'])
126
+ df['ticker'] = symbol
127
+ df['time'] = df['time'].dt.tz_localize('UTC').dt.tz_convert('Asia/Ho_Chi_Minh')
128
+ if resolution == '1D':
129
+ df['time'] = df['time'].dt.date
130
+ else:
131
+ df['time'] = df['time'].dt.strftime('%Y-%m-%d %H:%M:%S')
132
+ df[['open', 'high', 'low', 'close']] = df[['open', 'high', 'low', 'close']].astype(float)
133
+ df['volume'] = df['volume'].astype(int)
134
+ return df
quant_agent/trading.py ADDED
@@ -0,0 +1,110 @@
1
+ """
2
+ Trading-side data: live price board, price depth (order book), and intraday
3
+ matched trades.
4
+
5
+ Data sources:
6
+ - Price depth (order book) -> VPS.
7
+ - Price board, intraday trades -> VCI (VietCap Securities).
8
+ """
9
+
10
+ from .config import *
11
+
12
+
13
+ # =============================================================================
14
+ # TRADING PRICE TABLE
15
+ # =============================================================================
16
+
17
+ def price_depth (stock_list='VPB,TCB', headers=vps_headers):
18
+ """
19
+ This function returns the trading price board of a target stocks list.
20
+ Args:
21
+ stock_list (:obj:`str`, required): STRING list of symbols separated by "," without any space. Ex: "TCB,SSI,BID"
22
+ """
23
+ url = f"https://bgapidatafeed.vps.com.vn/getliststockdata/{stock_list}"
24
+ data = fetch_json('GET', url, headers=headers, data={}, source='VPS')
25
+ if data is None:
26
+ return None
27
+ df = json_normalize(data)
28
+ df.rename(columns={'sym': 'Mã CP', 'c': 'Giá Trần', 'f': 'Giá Sàn', 'r': 'Giá tham chiếu', 'lot': 'Tổng Khối Lượng', 'highPrice': 'Giá cao', 'lowPrice': 'Giá thấp', 'avePrice': 'Giá TB', 'lastPrice': 'Giá khớp lệnh', 'lastVolume': 'KL Khớp lệnh', 'ot': '+/- (Khớp lệnh)', 'changePc': '% (Khớp lệnh)', 'fBVol': 'ĐTNN Mua', 'fSVolume': 'ĐTNN Bán', 'fRoom': 'ĐTNN Room'}, inplace=True)
29
+ # columns g1..g6 pack "price|volume" for 3 bid + 3 ask levels
30
+ df['Giá mua 3'] = df['g1'].str.split('|').str[0]
31
+ df['KL mua 3'] = df['g1'].str.split('|').str[1]
32
+ df['Giá mua 2'] = df['g2'].str.split('|').str[0]
33
+ df['KL mua 2'] = df['g2'].str.split('|').str[1]
34
+ df['Giá mua 1'] = df['g3'].str.split('|').str[0]
35
+ df['KL mua 1'] = df['g3'].str.split('|').str[1]
36
+ df['Giá bán 1'] = df['g4'].str.split('|').str[0]
37
+ df['KL bán 1'] = df['g4'].str.split('|').str[1]
38
+ df['Giá bán 2'] = df['g5'].str.split('|').str[0]
39
+ df['KL bán 2'] = df['g5'].str.split('|').str[1]
40
+ df['Giá bán 3'] = df['g6'].str.split('|').str[0]
41
+ df['KL bán 3'] = df['g6'].str.split('|').str[1]
42
+ df.drop(columns=['id', 'mc', 'g1', 'g2', 'g3', 'g4', 'g5', 'g6'], inplace=True)
43
+ df = df[['Mã CP', 'Giá tham chiếu', 'Giá Trần', 'Giá Sàn', 'Giá mua 3', 'KL mua 3', 'Giá mua 2', 'KL mua 2', 'Giá mua 1', 'KL mua 1', 'Giá khớp lệnh', 'KL Khớp lệnh', 'Giá bán 1', 'KL bán 1', 'Giá bán 2', 'KL bán 2', 'Giá bán 3', 'KL bán 3', 'Tổng Khối Lượng', 'ĐTNN Mua', 'ĐTNN Bán', 'ĐTNN Room']]
44
+ # "Giá*" columns are quoted in thousands VND; convert to whole VND
45
+ for col in df.columns:
46
+ if 'Giá' in col:
47
+ try:
48
+ df[col] = (df[col].astype(float) * 1000).astype(int)
49
+ except (ValueError, TypeError):
50
+ pass
51
+ return df
52
+
53
+ def price_board (symbol_ls):
54
+ """
55
+ This function returns the trading price board of a target stocks list.
56
+ Data source: VCI (VietCap Securities). Live bid/ask ladder + match price.
57
+ Args:
58
+ symbol_ls (:obj:`str`, required): STRING list of symbols separated by "," without any space. Ex: "TCB,SSI,BID"
59
+ """
60
+ symbols = [s.strip() for s in symbol_ls.split(',') if s.strip()] if isinstance(symbol_ls, str) else list(symbol_ls)
61
+ url = "https://trading.vietcap.com.vn/api/price/symbols/getList"
62
+ data = fetch_json('POST', url, headers=vci_headers, data=json.dumps({"symbols": symbols}), source='VCI')
63
+ if not data:
64
+ print(f'No price board data returned by VCI for {symbols}.')
65
+ return None
66
+ rows = []
67
+ for item in data:
68
+ row = {}
69
+ listing = item.get('listingInfo', {}) or {}
70
+ row['ticker'] = listing.get('ticker')
71
+ row['exchange'] = listing.get('board')
72
+ match = item.get('matchPrice', {}) or {}
73
+ row['matchPrice'] = match.get('matchPrice')
74
+ row['matchVolume'] = match.get('matchVol')
75
+ bid_ask = item.get('bidAsk', {}) or {}
76
+ for i, level in enumerate(bid_ask.get('bidPrices', [])[:3], start=1):
77
+ row[f'bidPrice{i}'] = level.get('price')
78
+ row[f'bidVolume{i}'] = level.get('volume')
79
+ for i, level in enumerate(bid_ask.get('askPrices', [])[:3], start=1):
80
+ row[f'askPrice{i}'] = level.get('price')
81
+ row[f'askVolume{i}'] = level.get('volume')
82
+ rows.append(row)
83
+ return pd.DataFrame(rows)
84
+
85
+ def stock_intraday_data (symbol='ACB', page_size=100, page=0, investor_segment=True, headers=None):
86
+ """
87
+ Get intraday matched-trade data for a stock.
88
+ Data source: VCI (VietCap Securities).
89
+ Parameters:
90
+ symbol (str): ticker of the stock
91
+ page_size (int): number of matched trades to fetch (VCI returns them most-recent-first). Default is 100.
92
+ page (int): unused.
93
+ investor_segment (bool): unused.
94
+ headers (dict): unused.
95
+ """
96
+ url = "https://trading.vietcap.com.vn/api/market-watch/LEData/getAll"
97
+ payload = {"symbol": symbol, "limit": page_size, "truncTime": None}
98
+ records = fetch_json('POST', url, headers=vci_headers, data=json.dumps(payload), source='VCI')
99
+ if not records:
100
+ print(f'No intraday data returned by VCI for {symbol}.')
101
+ return None
102
+ df = pd.DataFrame(records)
103
+ df = df.rename(columns={'truncTime': 'time', 'matchPrice': 'price', 'matchVol': 'volume', 'matchType': 'orderType'})
104
+ df['time'] = pd.to_datetime(df['time'].astype(float), unit='s').dt.tz_localize('UTC').dt.tz_convert('Asia/Ho_Chi_Minh')
105
+ df['ticker'] = symbol
106
+ for col in ['price', 'volume']:
107
+ if col in df.columns:
108
+ df[col] = df[col].astype(float)
109
+ keep_cols = [c for c in ['ticker', 'time', 'orderType', 'volume', 'price'] if c in df.columns]
110
+ return df[keep_cols].reset_index(drop=True)
quant_agent/utils.py ADDED
@@ -0,0 +1,53 @@
1
+ """
2
+ Generic date helper not tied to any specific data source. For the
3
+ `today`/`last_xd`/`start_xm` helpers used as default `start_date`/`end_date`
4
+ values elsewhere in this package, see `config.py` instead.
5
+ """
6
+
7
+ from datetime import datetime, timedelta
8
+ from dateutil.relativedelta import relativedelta
9
+
10
+ def get_date(n, unit):
11
+ """
12
+ Return YYYY-mm-dd value from today to n days, months or years in the past
13
+ Parameters:
14
+ n: number of days, months or years
15
+ unit: 'day', 'month' or 'year'
16
+ """
17
+ if unit == 'day':
18
+ return (datetime.now() - timedelta(days=n)).strftime('%Y-%m-%d')
19
+ elif unit == 'month':
20
+ return (datetime.now() - relativedelta(months=n)).strftime('%Y-%m-%d')
21
+ elif unit == 'year':
22
+ return (datetime.now() - relativedelta(years=n)).strftime('%Y-%m-%d')
23
+
24
+ # def previous_weekday (date):
25
+ # date_value = datetime.strptime(date, '%Y-%m-%d')
26
+ # weekday_name = date_value.strftime('%a')
27
+ # if weekday_name == 'Sun':
28
+ # prev_weekday_date = date_value - timedelta(days=2)
29
+ # elif weekday_name == 'Mon':
30
+ # prev_weekday_date = date_value - timedelta(days=3)
31
+ # else:
32
+ # prev_weekday_date = date_value - timedelta(days=1)
33
+ # return prev_weekday_date
34
+
35
+ # def countBack (start_date, end_date):
36
+ # fd_value = datetime.strptime(start_date, '%Y-%m-%d')
37
+ # td_value = datetime.strptime(end_date, '%Y-%m-%d')
38
+ # diff = td_value - fd_value # Calculate the difference
39
+ # seconds = diff.total_seconds() # Get the total number of seconds
40
+ # # Define the number of seconds in each scenario
41
+ # minute = 60
42
+ # quarter_hour = 15 * minute
43
+ # hour = 60 * minute
44
+ # day = 60 * 6 * minute # trading time from 9AM to 3PM which is 6 hours
45
+ # week = 6 * day
46
+ # month = 23 * day # maximum of 23 working days in a month
47
+ # # Calculate the diff in each scenario
48
+ # diff_minute = round(seconds / minute)
49
+ # diff_quarter_hour = round(seconds / quarter_hour)
50
+ # diff_hour = round(seconds / hour)
51
+ # diff_week = round(seconds / week)
52
+ # diff_month = round(seconds / month)
53
+ # return diff_minute, diff_quarter_hour, diff_hour, diff_week, diff_month