quantvn 0.1.9__tar.gz → 0.1.10__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.
- {quantvn-0.1.9/quantvn.egg-info → quantvn-0.1.10}/PKG-INFO +1 -1
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/data/stocks.py +62 -223
- {quantvn-0.1.9 → quantvn-0.1.10/quantvn.egg-info}/PKG-INFO +1 -1
- {quantvn-0.1.9 → quantvn-0.1.10}/setup.py +1 -1
- {quantvn-0.1.9 → quantvn-0.1.10}/LICENSE +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/MANIFEST.in +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/README.md +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/data/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/data/derivatives.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/data/download.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/metrics/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/metrics/backtest.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/crypto/metrics/metrics.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/metrics/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/metrics/portfolio.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/metrics/single_asset.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/metrics/st.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/paper/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/paper/portfolio.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/paper/single_asset.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/data/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/data/const.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/data/core.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/data/derivatives.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/data/utils.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/metrics/__init__.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/metrics/backtest.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn/vn/metrics/metrics.py +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn.egg-info/SOURCES.txt +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn.egg-info/dependency_links.txt +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn.egg-info/requires.txt +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/quantvn.egg-info/top_level.txt +0 -0
- {quantvn-0.1.9 → quantvn-0.1.10}/setup.cfg +0 -0
|
@@ -1055,75 +1055,48 @@ class Company:
|
|
|
1055
1055
|
return self._provider.ratio_summary()
|
|
1056
1056
|
|
|
1057
1057
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
BALANCE_SHEET_QUERY = """
|
|
1093
|
-
query Query($ticker: String!, $period: Int!) {
|
|
1094
|
-
BalanceSheet(ticker: $ticker, period: $period) {
|
|
1095
|
-
id
|
|
1096
|
-
ticker
|
|
1097
|
-
year
|
|
1098
|
-
quarter
|
|
1099
|
-
totalAssets
|
|
1100
|
-
currentAssets
|
|
1101
|
-
nonCurrentAssets
|
|
1102
|
-
totalLiabilities
|
|
1103
|
-
currentLiabilities
|
|
1104
|
-
nonCurrentLiabilities
|
|
1105
|
-
totalEquity
|
|
1106
|
-
shareCapital
|
|
1107
|
-
retainedEarnings
|
|
1108
|
-
__typename
|
|
1109
|
-
}
|
|
1058
|
+
# ===== VCI GraphQL Finance Ratio Query =====
|
|
1059
|
+
FINANCE_RATIO_QUERY = """
|
|
1060
|
+
fragment Ratios on CompanyFinancialRatio {
|
|
1061
|
+
ticker
|
|
1062
|
+
yearReport
|
|
1063
|
+
lengthReport
|
|
1064
|
+
updateDate
|
|
1065
|
+
revenue
|
|
1066
|
+
revenueGrowth
|
|
1067
|
+
netProfit
|
|
1068
|
+
netProfitGrowth
|
|
1069
|
+
ebitMargin
|
|
1070
|
+
roe
|
|
1071
|
+
roic
|
|
1072
|
+
roa
|
|
1073
|
+
pe
|
|
1074
|
+
pb
|
|
1075
|
+
eps
|
|
1076
|
+
currentRatio
|
|
1077
|
+
cashRatio
|
|
1078
|
+
quickRatio
|
|
1079
|
+
interestCoverage
|
|
1080
|
+
netProfitMargin
|
|
1081
|
+
grossMargin
|
|
1082
|
+
ev
|
|
1083
|
+
issueShare
|
|
1084
|
+
ps
|
|
1085
|
+
pcf
|
|
1086
|
+
bvps
|
|
1087
|
+
evPerEbitda
|
|
1088
|
+
charterCapital
|
|
1089
|
+
dividend
|
|
1090
|
+
ebitda
|
|
1091
|
+
ebit
|
|
1110
1092
|
}
|
|
1111
|
-
""".strip()
|
|
1112
1093
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
quarter
|
|
1120
|
-
netCashFromOperating
|
|
1121
|
-
netCashFromInvesting
|
|
1122
|
-
netCashFromFinancing
|
|
1123
|
-
netCashFlow
|
|
1124
|
-
cashAtBeginning
|
|
1125
|
-
cashAtEnd
|
|
1126
|
-
__typename
|
|
1094
|
+
query Query($ticker: String!, $period: String!) {
|
|
1095
|
+
CompanyFinancialRatio(ticker: $ticker, period: $period) {
|
|
1096
|
+
ratio {
|
|
1097
|
+
...Ratios
|
|
1098
|
+
}
|
|
1099
|
+
period
|
|
1127
1100
|
}
|
|
1128
1101
|
}
|
|
1129
1102
|
""".strip()
|
|
@@ -1132,84 +1105,19 @@ query Query($ticker: String!, $period: Int!) {
|
|
|
1132
1105
|
class _FinanceProvider:
|
|
1133
1106
|
"""Internal provider interface (do not use directly)."""
|
|
1134
1107
|
|
|
1135
|
-
def
|
|
1136
|
-
self, period: str = "
|
|
1137
|
-
) -> pd.DataFrame: # pragma: no cover
|
|
1138
|
-
raise NotImplementedError
|
|
1139
|
-
|
|
1140
|
-
def balance_sheet(
|
|
1141
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1142
|
-
) -> pd.DataFrame: # pragma: no cover
|
|
1143
|
-
raise NotImplementedError
|
|
1144
|
-
|
|
1145
|
-
def cash_flow(
|
|
1146
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1108
|
+
def ratio(
|
|
1109
|
+
self, period: str = "Q", dropna: bool = False
|
|
1147
1110
|
) -> pd.DataFrame: # pragma: no cover
|
|
1148
1111
|
raise NotImplementedError
|
|
1149
1112
|
|
|
1150
1113
|
|
|
1151
1114
|
class _VCIFinanceProvider(_FinanceProvider):
|
|
1152
|
-
"""Finance provider via VCI GraphQL API."""
|
|
1115
|
+
"""Finance provider via VCI GraphQL API - only supports ratio()."""
|
|
1153
1116
|
|
|
1154
1117
|
def __init__(self, symbol: str, *, lang: str = "vi"):
|
|
1155
1118
|
self.symbol = str(symbol).upper().strip()
|
|
1156
1119
|
self.lang = lang or "vi"
|
|
1157
|
-
self._cache: Dict[str, Dict[str, Any]] = {}
|
|
1158
|
-
|
|
1159
|
-
def _fetch_finance(self, query: str, report_type: str, period: str) -> pd.DataFrame:
|
|
1160
|
-
"""Fetch financial statement via GraphQL and return as DataFrame."""
|
|
1161
|
-
period_val = PERIOD_MAP.get(period, 1)
|
|
1162
|
-
|
|
1163
|
-
cache_key = f"{report_type}_{period}"
|
|
1164
|
-
if cache_key not in self._cache:
|
|
1165
|
-
try:
|
|
1166
|
-
data = _vci_graphql_request(
|
|
1167
|
-
query=query,
|
|
1168
|
-
variables={"ticker": self.symbol, "period": period_val},
|
|
1169
|
-
)
|
|
1170
|
-
self._cache[cache_key] = data
|
|
1171
|
-
except Exception as e:
|
|
1172
|
-
raise ValueError(f"Failed to fetch {report_type} from VCI: {e}")
|
|
1173
|
-
|
|
1174
|
-
data = self._cache[cache_key]
|
|
1175
|
-
items = data.get(report_type) or []
|
|
1176
|
-
|
|
1177
|
-
if not isinstance(items, list) or not items:
|
|
1178
|
-
return pd.DataFrame()
|
|
1179
|
-
|
|
1180
|
-
df = pd.DataFrame(items)
|
|
1181
|
-
return df
|
|
1182
|
-
|
|
1183
|
-
def income_statement(
|
|
1184
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1185
|
-
) -> pd.DataFrame:
|
|
1186
|
-
"""Fetch income statement."""
|
|
1187
|
-
df = self._fetch_finance(INCOME_STATEMENT_QUERY, "IncomeStatement", period)
|
|
1188
|
-
if dropna and not df.empty:
|
|
1189
|
-
df = df.dropna(axis=1, how="all")
|
|
1190
|
-
return df
|
|
1191
|
-
|
|
1192
|
-
def balance_sheet(
|
|
1193
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1194
|
-
) -> pd.DataFrame:
|
|
1195
|
-
"""Fetch balance sheet."""
|
|
1196
|
-
df = self._fetch_finance(BALANCE_SHEET_QUERY, "BalanceSheet", period)
|
|
1197
|
-
if dropna and not df.empty:
|
|
1198
|
-
df = df.dropna(axis=1, how="all")
|
|
1199
|
-
return df
|
|
1200
1120
|
|
|
1201
|
-
def cash_flow(
|
|
1202
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1203
|
-
) -> pd.DataFrame:
|
|
1204
|
-
"""Fetch cash flow statement."""
|
|
1205
|
-
df = self._fetch_finance(CASH_FLOW_QUERY, "CashFlow", period)
|
|
1206
|
-
if dropna and not df.empty:
|
|
1207
|
-
df = df.dropna(axis=1, how="all")
|
|
1208
|
-
return df
|
|
1209
|
-
|
|
1210
|
-
# ------------------------------------------------------------------
|
|
1211
|
-
# Extra: Financial ratios from VCI (CompanyFinancialRatio)
|
|
1212
|
-
# ------------------------------------------------------------------
|
|
1213
1121
|
def ratio(self, period: str = "Q", dropna: bool = False) -> pd.DataFrame:
|
|
1214
1122
|
"""
|
|
1215
1123
|
Fetch financial ratios from VCI CompanyFinancialRatio API.
|
|
@@ -1253,60 +1161,17 @@ class _VCIFinanceProvider(_FinanceProvider):
|
|
|
1253
1161
|
return df
|
|
1254
1162
|
|
|
1255
1163
|
|
|
1256
|
-
class _TCBSFinanceProvider(_FinanceProvider):
|
|
1257
|
-
"""Finance provider via TCBS tcanalysis endpoints (kept as fallback)."""
|
|
1258
|
-
|
|
1259
|
-
def __init__(self, symbol: str):
|
|
1260
|
-
self.symbol = str(symbol).upper().strip()
|
|
1261
|
-
|
|
1262
|
-
def _fetch(
|
|
1263
|
-
self, report: str, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1264
|
-
) -> pd.DataFrame:
|
|
1265
|
-
BASE = "https://apipubaws.tcbs.com.vn"
|
|
1266
|
-
ANALYSIS = "tcanalysis"
|
|
1267
|
-
assert report in FIN_MAP, f"Invalid report: {report}"
|
|
1268
|
-
url = f"{BASE}/{ANALYSIS}/v1/finance/{self.symbol}/{FIN_MAP[report]}"
|
|
1269
|
-
params = {"period": PERIOD_MAP.get(period, 1), "size": 1000}
|
|
1270
|
-
data = send_request(url, params=params)
|
|
1271
|
-
df = pd.DataFrame(data)
|
|
1272
|
-
if dropna:
|
|
1273
|
-
df = df.dropna(axis=1, how="all")
|
|
1274
|
-
return df
|
|
1275
|
-
|
|
1276
|
-
def income_statement(
|
|
1277
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1278
|
-
) -> pd.DataFrame:
|
|
1279
|
-
return self._fetch("income_statement", period, lang, dropna)
|
|
1280
|
-
|
|
1281
|
-
def balance_sheet(
|
|
1282
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1283
|
-
) -> pd.DataFrame:
|
|
1284
|
-
return self._fetch("balance_sheet", period, lang, dropna)
|
|
1285
|
-
|
|
1286
|
-
def cash_flow(
|
|
1287
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1288
|
-
) -> pd.DataFrame:
|
|
1289
|
-
return self._fetch("cash_flow", period, lang, dropna)
|
|
1290
|
-
|
|
1291
|
-
def ratio(self, period: str = "Q", dropna: bool = False) -> pd.DataFrame:
|
|
1292
|
-
"""
|
|
1293
|
-
TCBS currently does not expose the same CompanyFinancialRatio GraphQL;
|
|
1294
|
-
keep a simple stub that returns empty DataFrame for symmetry.
|
|
1295
|
-
"""
|
|
1296
|
-
return pd.DataFrame()
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
1164
|
class Finance:
|
|
1300
1165
|
"""
|
|
1301
|
-
Public Finance API
|
|
1166
|
+
Public Finance API - Tỷ số tài chính từ VCI GraphQL API.
|
|
1302
1167
|
|
|
1303
|
-
|
|
1304
|
-
-
|
|
1305
|
-
- balance_sheet(period="year", lang="vi", dropna=False)
|
|
1306
|
-
- cash_flow(period="year", lang="vi", dropna=False)
|
|
1307
|
-
- ratio(period="Q" | "Y", dropna=False) # via VCI CompanyFinancialRatio
|
|
1168
|
+
Chỉ hỗ trợ:
|
|
1169
|
+
- ratio(period="Q" | "Y", dropna=False) # Tỷ số tài chính từ VCI CompanyFinancialRatio
|
|
1308
1170
|
|
|
1309
|
-
|
|
1171
|
+
Args:
|
|
1172
|
+
symbol: Mã cổ phiếu (VD: "HPG", "VIC")
|
|
1173
|
+
source: Nguồn dữ liệu (chỉ hỗ trợ "VCI")
|
|
1174
|
+
lang: Ngôn ngữ (mặc định "vi")
|
|
1310
1175
|
"""
|
|
1311
1176
|
|
|
1312
1177
|
def __init__(self, symbol: str, source: str = "VCI", lang: str = "vi"):
|
|
@@ -1314,51 +1179,25 @@ class Finance:
|
|
|
1314
1179
|
self.source = (source or "VCI").upper()
|
|
1315
1180
|
self.lang = lang or "vi"
|
|
1316
1181
|
|
|
1317
|
-
if self.source
|
|
1318
|
-
|
|
1319
|
-
self.symbol, lang=self.lang
|
|
1320
|
-
)
|
|
1321
|
-
elif self.source == "TCBS":
|
|
1322
|
-
self._provider = _TCBSFinanceProvider(self.symbol)
|
|
1323
|
-
elif self.source == "AUTO":
|
|
1324
|
-
# Try VCI first; if it fails, fallback to TCBS.
|
|
1325
|
-
try:
|
|
1326
|
-
self._provider = _VCIFinanceProvider(self.symbol, lang=self.lang)
|
|
1327
|
-
_ = self._provider.income_statement()
|
|
1328
|
-
except Exception:
|
|
1329
|
-
self._provider = _TCBSFinanceProvider(self.symbol)
|
|
1330
|
-
else:
|
|
1331
|
-
raise ValueError("source must be one of: 'VCI', 'TCBS', 'AUTO'")
|
|
1332
|
-
|
|
1333
|
-
def income_statement(
|
|
1334
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1335
|
-
) -> pd.DataFrame:
|
|
1336
|
-
return self._provider.income_statement(period=period, lang=lang, dropna=dropna)
|
|
1337
|
-
|
|
1338
|
-
def balance_sheet(
|
|
1339
|
-
self, period: str = "year", lang: str = "vi", dropna: bool = False
|
|
1340
|
-
) -> pd.DataFrame:
|
|
1341
|
-
return self._provider.balance_sheet(period=period, lang=lang, dropna=dropna)
|
|
1182
|
+
if self.source != "VCI":
|
|
1183
|
+
raise ValueError("Finance class chỉ hỗ trợ source='VCI' (VCI GraphQL API)")
|
|
1342
1184
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
return self._provider.cash_flow(period=period, lang=lang, dropna=dropna)
|
|
1185
|
+
self._provider: _FinanceProvider = _VCIFinanceProvider(
|
|
1186
|
+
self.symbol, lang=self.lang
|
|
1187
|
+
)
|
|
1347
1188
|
|
|
1348
1189
|
def ratio(self, period: str = "Q", dropna: bool = False) -> pd.DataFrame:
|
|
1349
1190
|
"""
|
|
1350
|
-
|
|
1191
|
+
Tỷ số tài chính từ VCI CompanyFinancialRatio endpoint.
|
|
1351
1192
|
|
|
1352
1193
|
Args:
|
|
1353
|
-
period: "Q" (
|
|
1194
|
+
period: "Q" (quý) hoặc "Y" (năm)
|
|
1195
|
+
dropna: Xóa các cột hoàn toàn trống
|
|
1196
|
+
|
|
1197
|
+
Returns:
|
|
1198
|
+
DataFrame chứa các tỷ số tài chính như revenue, netProfit, roe, pe, pb, etc.
|
|
1354
1199
|
"""
|
|
1355
|
-
|
|
1356
|
-
vci_provider: _VCIFinanceProvider
|
|
1357
|
-
if isinstance(self._provider, _VCIFinanceProvider):
|
|
1358
|
-
vci_provider = self._provider
|
|
1359
|
-
else:
|
|
1360
|
-
vci_provider = _VCIFinanceProvider(self.symbol, lang=self.lang)
|
|
1361
|
-
return vci_provider.ratio(period=period, dropna=dropna)
|
|
1200
|
+
return self._provider.ratio(period=period, dropna=dropna)
|
|
1362
1201
|
|
|
1363
1202
|
|
|
1364
1203
|
# ===================== Reorganized: Fund =====================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|