Skip to content

Commit c5276c9

Browse files
committed
1)Calculate 1m quote from tick
2)Add quote stats and ts api 3)Looser constraints on tag
1 parent db69c41 commit c5276c9

31 files changed

+551
-203
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
GET http://127.0.0.1:8090/api/trading/get_quote_stats
2+
accept: application/json

api-tests/trading/query_kdata.http

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
POST http://127.0.0.1:8090/api/trading/query_kdata
2+
accept: application/json
3+
Content-Type: application/json
4+
5+
6+
{
7+
"data_provider": "em",
8+
"entity_ids": [
9+
"stock_sz_002085",
10+
"stock_sz_300133"
11+
],
12+
"adjust_type": "hfq"
13+
}

api-tests/factor/query_kdata.http renamed to api-tests/trading/query_ts.http

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
POST http://127.0.0.1:8090/api/factor/query_kdata
1+
POST http://127.0.0.1:8090/api/trading/query_ts
22
accept: application/json
33
Content-Type: application/json
44

src/zvt/api/kdata.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@
99
from zvt.contract.api import decode_entity_id, get_schema_by_name
1010
from zvt.domain import Index1dKdata
1111
from zvt.utils.pd_utils import pd_is_not_null
12-
from zvt.utils.time_utils import to_time_str, TIME_FORMAT_DAY, TIME_FORMAT_ISO8601, to_pd_timestamp
12+
from zvt.utils.time_utils import (
13+
to_time_str,
14+
TIME_FORMAT_DAY,
15+
TIME_FORMAT_ISO8601,
16+
to_pd_timestamp,
17+
date_time_by_interval,
18+
current_date,
19+
)
1320

1421

15-
def get_trade_dates(start, end):
22+
def get_trade_dates(start, end=None):
1623
df = Index1dKdata.query_data(
1724
entity_id="index_sh_000001",
1825
provider="em",
@@ -25,6 +32,12 @@ def get_trade_dates(start, end):
2532
return df["timestamp"].tolist()
2633

2734

35+
def get_recent_trade_dates(days_count=5):
36+
max_start = date_time_by_interval(current_date(), -days_count - 15)
37+
dates = get_trade_dates(start=max_start)
38+
return dates[-days_count:]
39+
40+
2841
def get_latest_kdata_date(
2942
entity_type: str,
3043
provider: str = None,
@@ -185,9 +198,14 @@ def to_sum(s):
185198
return df
186199

187200

201+
if __name__ == "__main__":
202+
print(get_recent_trade_dates())
203+
204+
188205
# the __all__ is generated
189206
__all__ = [
190207
"get_trade_dates",
208+
"get_recent_trade_dates",
191209
"get_latest_kdata_date",
192210
"get_kdata_schema",
193211
"get_kdata",

src/zvt/broker/qmt/qmt_quote.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@
99
from zvt.contract import IntervalLevel, AdjustType
1010
from zvt.contract.api import decode_entity_id, df_to_db, get_db_session
1111
from zvt.domain import StockQuote, Stock
12-
from zvt.domain.quotes.stock.stock_quote import Stock1mQuote
12+
from zvt.domain.quotes.stock.stock_quote import Stock1mQuote, StockQuoteLog
1313
from zvt.utils.pd_utils import pd_is_not_null
14-
from zvt.utils.time_utils import to_time_str, current_date, to_pd_timestamp, now_pd_timestamp, TIME_FORMAT_MINUTE
14+
from zvt.utils.time_utils import (
15+
to_time_str,
16+
current_date,
17+
to_pd_timestamp,
18+
now_pd_timestamp,
19+
TIME_FORMAT_MINUTE,
20+
date_time_by_interval,
21+
)
1522

1623
# https://dict.thinktrader.net/nativeApi/start_now.html?id=e2M5nZ
1724

@@ -137,7 +144,7 @@ def get_kdata(
137144
code = _to_qmt_code(entity_id=entity_id)
138145
period = level.value
139146
# 保证qmt先下载数据到本地
140-
xtdata.download_history_data(stock_code=code, period=period, start_time="", end_time="")
147+
xtdata.download_history_data(stock_code=code, period=period)
141148
records = xtdata.get_market_data(
142149
stock_list=[code],
143150
period=period,
@@ -223,14 +230,22 @@ def on_data(datas, stock_df=entity_df):
223230
df["float_cap"] = df["float_volume"] * df["price"]
224231
df["total_cap"] = df["total_volume"] * df["price"]
225232

233+
# 实时行情统计,只保留最新
226234
df_to_db(df, data_schema=StockQuote, provider="qmt", force_update=True, drop_duplicates=False)
227-
cost_time = time.time() - start_time
228-
logger.info(f"Quotes cost_time:{cost_time} for {len(datas.keys())} stocks")
229235

236+
# 1分钟分时
230237
df["id"] = df[["entity_id", "timestamp"]].apply(
231238
lambda se: "{}_{}".format(se["entity_id"], to_time_str(se["timestamp"], TIME_FORMAT_MINUTE)), axis=1
232239
)
233240
df_to_db(df, data_schema=Stock1mQuote, provider="qmt", force_update=True, drop_duplicates=False)
241+
# 历史记录
242+
# df["id"] = df[["entity_id", "timestamp"]].apply(
243+
# lambda se: "{}_{}".format(se["entity_id"], to_time_str(se["timestamp"], TIME_FORMAT_MINUTE2)), axis=1
244+
# )
245+
# df_to_db(df, data_schema=StockQuoteLog, provider="qmt", force_update=True, drop_duplicates=False)
246+
247+
cost_time = time.time() - start_time
248+
logger.info(f"Quotes cost_time:{cost_time} for {len(datas.keys())} stocks")
234249

235250
return on_data
236251

@@ -245,6 +260,9 @@ def download_capital_data():
245260
def clear_history_quote():
246261
session = get_db_session("qmt", data_schema=StockQuote)
247262
session.query(StockQuote).filter(StockQuote.timestamp < current_date()).delete()
263+
start_date = date_time_by_interval(current_date(), -20)
264+
session.query(Stock1mQuote).filter(Stock1mQuote.timestamp < start_date).delete()
265+
session.query(StockQuoteLog).filter(StockQuoteLog.timestamp < start_date).delete()
248266
session.commit()
249267

250268

@@ -253,7 +271,7 @@ def record_tick():
253271
Stock.record_data(provider="em")
254272
stocks = get_qmt_stocks()
255273
print(stocks)
256-
xtdata.subscribe_whole_quote(stocks, callback=tick_to_quote())
274+
sid = xtdata.subscribe_whole_quote(stocks, callback=tick_to_quote())
257275

258276
"""阻塞线程接收行情回调"""
259277
import time
@@ -267,6 +285,7 @@ def record_tick():
267285
if current_timestamp.hour >= 15 and current_timestamp.minute >= 10:
268286
logger.info(f"record tick finished at: {current_timestamp}")
269287
break
288+
xtdata.unsubscribe_quote(sid)
270289

271290

272291
if __name__ == "__main__":
@@ -278,7 +297,6 @@ def record_tick():
278297
sched.start()
279298
sched._thread.join()
280299

281-
282300
# the __all__ is generated
283301
__all__ = [
284302
"get_qmt_stocks",

src/zvt/contract/recorder.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@
1313
from zvt.contract.api import get_entities, get_data
1414
from zvt.contract.base_service import OneStateService
1515
from zvt.contract.schema import Mixin, TradableEntity
16+
from zvt.contract.utils import is_in_same_interval, evaluate_size_from_timestamp
1617
from zvt.contract.zvt_info import RecorderState
1718
from zvt.utils.pd_utils import pd_is_not_null
1819
from zvt.utils.time_utils import (
1920
to_pd_timestamp,
2021
TIME_FORMAT_DAY,
2122
to_time_str,
22-
evaluate_size_from_timestamp,
23-
is_in_same_interval,
2423
now_pd_timestamp,
2524
now_time_str,
2625
)

src/zvt/contract/utils.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -*- coding: utf-8 -*-
2+
import math
3+
4+
import pandas as pd
5+
6+
from zvt.contract import IntervalLevel
7+
from zvt.utils.time_utils import to_pd_timestamp
8+
9+
10+
def is_in_same_interval(t1: pd.Timestamp, t2: pd.Timestamp, level: IntervalLevel):
11+
t1 = to_pd_timestamp(t1)
12+
t2 = to_pd_timestamp(t2)
13+
if level == IntervalLevel.LEVEL_1WEEK:
14+
return t1.week == t2.week
15+
if level == IntervalLevel.LEVEL_1MON:
16+
return t1.month == t2.month
17+
18+
return level.floor_timestamp(t1) == level.floor_timestamp(t2)
19+
20+
21+
def evaluate_size_from_timestamp(
22+
start_timestamp, level: IntervalLevel, one_day_trading_minutes, end_timestamp: pd.Timestamp = None
23+
):
24+
"""
25+
given from timestamp,level,one_day_trading_minutes,this func evaluate size of kdata to current.
26+
it maybe a little bigger than the real size for fetching all the kdata.
27+
28+
:param start_timestamp:
29+
:type start_timestamp: pd.Timestamp
30+
:param level:
31+
:type level: IntervalLevel
32+
:param one_day_trading_minutes:
33+
:type one_day_trading_minutes: int
34+
"""
35+
if not end_timestamp:
36+
end_timestamp = pd.Timestamp.now()
37+
else:
38+
end_timestamp = to_pd_timestamp(end_timestamp)
39+
40+
time_delta = end_timestamp - to_pd_timestamp(start_timestamp)
41+
42+
one_day_trading_seconds = one_day_trading_minutes * 60
43+
44+
if level == IntervalLevel.LEVEL_1DAY:
45+
return time_delta.days + 1
46+
47+
if level == IntervalLevel.LEVEL_1WEEK:
48+
return int(math.ceil(time_delta.days / 7)) + 1
49+
50+
if level == IntervalLevel.LEVEL_1MON:
51+
return int(math.ceil(time_delta.days / 30)) + 1
52+
53+
if time_delta.days > 0:
54+
seconds = (time_delta.days + 1) * one_day_trading_seconds
55+
return int(math.ceil(seconds / level.to_second())) + 1
56+
else:
57+
seconds = time_delta.total_seconds()
58+
return min(int(math.ceil(seconds / level.to_second())) + 1, one_day_trading_seconds / level.to_second() + 1)
59+
60+
61+
def next_timestamp_on_level(current_timestamp: pd.Timestamp, level: IntervalLevel) -> pd.Timestamp:
62+
current_timestamp = to_pd_timestamp(current_timestamp)
63+
return current_timestamp + pd.Timedelta(seconds=level.to_second())
64+
65+
66+
def is_finished_kdata_timestamp(timestamp, level: IntervalLevel):
67+
timestamp = to_pd_timestamp(timestamp)
68+
if level.floor_timestamp(timestamp) == timestamp:
69+
return True
70+
return False

src/zvt/domain/quotes/stock/stock_quote.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,39 @@ class StockQuote(StockQuoteBase, Mixin):
4141
total_cap = Column(Float)
4242

4343

44+
class StockQuoteLog(StockQuoteBase, Mixin):
45+
__tablename__ = "stock_quote_log"
46+
code = Column(String(length=32))
47+
name = Column(String(length=32))
48+
49+
#: UNIX时间戳
50+
time = Column(Integer)
51+
#: 最新价
52+
price = Column(Float)
53+
# 涨跌幅
54+
change_pct = Column(Float)
55+
# 成交金额
56+
turnover = Column(Float)
57+
# 换手率
58+
turnover_rate = Column(Float)
59+
#: 是否涨停
60+
is_limit_up = Column(Boolean)
61+
#: 封涨停金额
62+
limit_up_amount = Column(Float)
63+
#: 是否跌停
64+
is_limit_down = Column(Boolean)
65+
#: 封跌停金额
66+
limit_down_amount = Column(Float)
67+
#: 5挡卖单金额
68+
ask_amount = Column(Float)
69+
#: 5挡买单金额
70+
bid_amount = Column(Float)
71+
#: 流通市值
72+
float_cap = Column(Float)
73+
#: 总市值
74+
total_cap = Column(Float)
75+
76+
4477
class Stock1mQuote(StockQuoteBase, Mixin):
4578
__tablename__ = "stock_1m_quote"
4679
code = Column(String(length=32))
@@ -60,10 +93,14 @@ class Stock1mQuote(StockQuoteBase, Mixin):
6093
turnover = Column(Float)
6194
# 换手率
6295
turnover_rate = Column(Float)
96+
#: 是否涨停
97+
is_limit_up = Column(Boolean)
98+
#: 是否跌停
99+
is_limit_down = Column(Boolean)
63100

64101

65102
register_schema(providers=["qmt"], db_name="stock_quote", schema_base=StockQuoteBase, entity_type="stock")
66103

67104

68105
# the __all__ is generated
69-
__all__ = ["StockQuote", "Stock1mQuote"]
106+
__all__ = ["StockQuote", "StockQuoteLog", "Stock1mQuote"]

src/zvt/factors/factor_models.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from pydantic import BaseModel, Field
66

7-
from zvt.contract import IntervalLevel, AdjustType
7+
from zvt.contract import IntervalLevel
88
from zvt.trader import TradingSignalType
99
from zvt.utils.time_utils import date_time_by_interval, current_date
1010

@@ -17,23 +17,6 @@ class FactorRequestModel(BaseModel):
1717
level: IntervalLevel = Field(default=IntervalLevel.LEVEL_1DAY)
1818

1919

20-
class KdataRequestModel(BaseModel):
21-
entity_ids: List[str]
22-
data_provider: str = Field(default="em")
23-
start_timestamp: datetime = Field(default=date_time_by_interval(current_date(), -365))
24-
end_timestamp: Optional[datetime] = Field(default=None)
25-
level: IntervalLevel = Field(default=IntervalLevel.LEVEL_1DAY)
26-
adjust_type: AdjustType = Field(default=AdjustType.hfq)
27-
28-
29-
class KdataModel(BaseModel):
30-
entity_id: str
31-
code: str
32-
name: str
33-
level: IntervalLevel = Field(default=IntervalLevel.LEVEL_1DAY)
34-
datas: List
35-
36-
3720
class TradingSignalModel(BaseModel):
3821
entity_id: str
3922
happen_timestamp: datetime
@@ -51,4 +34,4 @@ class FactorResultModel(BaseModel):
5134

5235

5336
# the __all__ is generated
54-
__all__ = ["FactorRequestModel", "KdataRequestModel", "KdataModel", "TradingSignalModel", "FactorResultModel"]
37+
__all__ = ["FactorRequestModel", "TradingSignalModel", "FactorResultModel"]

src/zvt/factors/factor_service.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,11 @@
11
# -*- coding: utf-8 -*-
22
import pandas as pd
33

4-
from zvt.api import kdata as kdata_api
54
from zvt.contract import zvt_context
65
from zvt.domain import Stock
7-
from zvt.factors.factor_models import FactorRequestModel, KdataRequestModel
6+
from zvt.factors.factor_models import FactorRequestModel
87
from zvt.factors.technical_factor import TechnicalFactor
98
from zvt.trader import TradingSignalType
10-
from zvt.utils.pd_utils import pd_is_not_null
11-
12-
13-
def query_kdata(kdata_request_model: KdataRequestModel):
14-
15-
kdata_df = kdata_api.get_kdata(
16-
entity_ids=kdata_request_model.entity_ids,
17-
provider=kdata_request_model.data_provider,
18-
start_timestamp=kdata_request_model.start_timestamp,
19-
end_timestamp=kdata_request_model.end_timestamp,
20-
adjust_type=kdata_request_model.adjust_type,
21-
)
22-
if pd_is_not_null(kdata_df):
23-
kdata_df["timestamp"] = kdata_df["timestamp"].apply(lambda x: int(x.timestamp()))
24-
kdata_df["data"] = kdata_df.apply(
25-
lambda x: x[
26-
["timestamp", "open", "high", "low", "close", "volume", "turnover", "change_pct", "turnover_rate"]
27-
].values.tolist(),
28-
axis=1,
29-
)
30-
df = kdata_df.groupby("entity_id").agg(
31-
code=("code", "first"),
32-
name=("name", "first"),
33-
level=("level", "first"),
34-
datas=("data", lambda data: list(data)),
35-
)
36-
df = df.reset_index(drop=False)
37-
return df.to_dict(orient="records")
389

3910

4011
def query_factor_result(factor_request_model: FactorRequestModel):
@@ -70,4 +41,4 @@ def to_trading_signal(order_type):
7041

7142

7243
# the __all__ is generated
73-
__all__ = ["query_kdata", "query_factor_result"]
44+
__all__ = ["query_factor_result"]

0 commit comments

Comments
 (0)