Skip to content

Commit 2f06ad6

Browse files
committed
Merge branch 'dev'
2 parents 3dfa6ed + 350d55e commit 2f06ad6

File tree

11 files changed

+234
-252
lines changed

11 files changed

+234
-252
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "quool"
7-
version = "7.0.5"
7+
version = "7.0.6"
88
description = "Quantitative Toolkit - a helper in quant developing"
99
readme = { file = "README.md", content-type = "text/markdown" }
1010
authors = [

quool/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616

1717
import quool.app as app
1818

19-
__version__ = "7.0.5"
19+
__version__ = "7.0.6"

quool/app/__init__.py

Lines changed: 8 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,12 @@
11
try:
2-
import argparse
3-
import importlib
4-
import pandas as pd
5-
import akshare as ak
6-
import streamlit as st
7-
from pathlib import Path
8-
from quool import ParquetManager
9-
from quool import Broker as QBroker
10-
import plotly.subplots as sp
11-
import plotly.graph_objects as go
2+
import quool.app.main as main
3+
import quool.app.runner as runner
4+
import quool.app.monitor as monitor
5+
import quool.app.strategy as strategy
6+
import quool.app.transact as transact
7+
import quool.app.performance as performance
8+
from .main import layout
9+
from .tool import LOG_PATH, BROKER_PATH, STRATEGIES_PATH, ASSET_PATH
1210

1311
except ImportError as e:
1412
print(e)
15-
16-
17-
def parsearg():
18-
parser = argparse.ArgumentParser()
19-
parser.add_argument('-r', "--refresh", type=int, default=60, help="refresh interval")
20-
parser.add_argument('-p', "--root", type=str, default="app", help="root path")
21-
parser.add_argument('-k', "--kline", type=int, default=60, help="how many klines kept in memory")
22-
args = parser.parse_args()
23-
return args
24-
25-
args = parsearg()
26-
REFRESH_INTERVAL = args.refresh
27-
ASSET_PATH = Path(args.root)
28-
TEMPLATE_PATH = Path(ASSET_PATH) / "template"
29-
BROKER_PATH = Path(ASSET_PATH) / "broker"
30-
STRATEGIES_PATH = Path(ASSET_PATH) / "strategy"
31-
LOG_PATH = Path(ASSET_PATH) / "log"
32-
KEEP_KLINE = args.kline
33-
34-
35-
def is_trading_time(time = None):
36-
time = pd.to_datetime(time or 'now')
37-
if (
38-
time.date() in ak.tool_trade_date_hist_sina().squeeze().to_list()
39-
and (
40-
(time.time() >= pd.to_datetime("09:30:00").time() and time.time() <= pd.to_datetime("11:30:00").time())
41-
or (time.time() >= pd.to_datetime("13:00:00").time() and time.time() <= pd.to_datetime("15:00:00").time())
42-
)
43-
):
44-
return True
45-
return False
46-
47-
def raw2ricequant(code: str):
48-
if code.startswith("6"):
49-
return code + ".XSHG"
50-
else:
51-
return code + ".XSHE"
52-
53-
def fetch_realtime(code_styler: callable = None):
54-
data = ak.stock_zh_a_spot_em().set_index("代码", drop=True).drop(columns="序号")
55-
data["open"] = data["最新价"]
56-
data["high"] = data["最新价"]
57-
data["low"] = data["最新价"]
58-
data["close"] = data["最新价"]
59-
data["volume"] = data["成交量"]
60-
if code_styler is not None:
61-
data.index = pd.MultiIndex.from_product([
62-
[pd.to_datetime("now")], data.index.map(code_styler)
63-
], names=["datetime", "code"])
64-
return data
65-
66-
def fetch_kline(symbol, format=True):
67-
data = ak.stock_zh_a_hist(symbol=symbol)
68-
if format:
69-
data = data.drop(columns="股票代码")
70-
data = data.rename(columns={
71-
"日期": "datetime", "开盘": "open", "最高": "high",
72-
"最低": "low", "收盘": "close", "成交量": "volume"
73-
})
74-
data["datetime"] = pd.to_datetime(data["datetime"])
75-
data.set_index("datetime", inplace=True)
76-
return data
77-
78-
def read_market(begin, end, backadj: bool = True, extra: str = None):
79-
if not (begin is None and end is None):
80-
begin = begin or pd.Timestamp("2015-01-01")
81-
end = end or pd.Timestamp.now()
82-
quotes_day = ParquetManager("D:/Documents/DataBase/quotes_day")
83-
data = quotes_day.read(
84-
date__ge=begin, date__le=end, index=["date", "code"],
85-
columns=["open", "high", "low", "close", "volume"] + (extra.split(',') if extra else [])
86-
)
87-
if backadj:
88-
adj = quotes_day.read(
89-
date__ge=begin, date__le=end, index=["date", "code"], columns=["adjfactor"]
90-
)
91-
data.loc[:, ["open", "high", "low", "close"]] = data[["open", "high", "low", "close"]].mul(adj["adjfactor"], axis=0)
92-
return data
93-
else:
94-
return None
95-
96-
def update_strategy(name):
97-
st.session_state.strategy = importlib.reload(importlib.import_module(
98-
str(STRATEGIES_PATH / name).replace("/", ".").replace("\\", ".")
99-
))
100-
params = getattr(st.session_state.strategy, "params", None)
101-
update = getattr(st.session_state.strategy, "update", None)
102-
init = getattr(st.session_state.strategy, "init", None)
103-
if params is None or update is None or init is None:
104-
raise ValueError("Strategy must have params, update and init functions")
105-
106-
def update_market():
107-
timepoints = st.session_state.timepoints
108-
market = fetch_realtime(st.session_state.styler)
109-
if timepoints.size >= KEEP_KLINE:
110-
st.session_state.market = pd.concat([
111-
st.session_state.market.loc[timepoints[-239]:, :], market
112-
])
113-
else:
114-
st.session_state.market = pd.concat([st.session_state.market, market])
115-
st.session_state.timepoints = st.session_state.market.index.get_level_values(0).unique()
116-
117-
@st.fragment(run_every=REFRESH_INTERVAL)
118-
def update_broker():
119-
broker = st.session_state.get('broker')
120-
strategy = st.session_state.get('strategy')
121-
strategy_stop = st.session_state.get('strategy_stop', True)
122-
strategy_args = st.session_state.get('strategy_args', {})
123-
if broker is not None and is_trading_time():
124-
update_market()
125-
market = st.session_state.market
126-
print(market)
127-
timepoints = market.index.get_level_values(0).unique()
128-
market_now = market.loc[timepoints[-1]].copy()
129-
market_pre = market.loc[timepoints[-2]]
130-
market_now["open"] = market_pre["close"]
131-
market_now["high"] = market_now["high"].max(market_pre["high"])
132-
market_now["low"] = market_now["low"].min(market_pre["low"])
133-
market_now["volume"] = market_now["volume"] - market_pre["volume"]
134-
if not strategy_stop:
135-
module = importlib.import_module(
136-
str(strategy).replace("/", ".").replace("\\", ".")[:-3]
137-
)
138-
getattr(module, "update")(broker, pd.to_datetime('now'), **strategy_args)
139-
broker.update(time=pd.to_datetime('now'), market=market_now)
140-
broker.store(BROKER_PATH / f"{broker.brokid}.json")
141-
elif broker is not None:
142-
broker.update(time=pd.to_datetime('now'), market=pd.DataFrame())
143-
broker.store(BROKER_PATH / f"{broker.brokid}.json")
144-
145-
@st.fragment(run_every=REFRESH_INTERVAL)
146-
def display_realtime():
147-
st.header("Realtime")
148-
market = st.session_state.market.loc[st.session_state.timepoints[-1]]
149-
selection = st.dataframe(market, selection_mode="single-row", on_select='rerun')
150-
if selection['selection']["rows"]:
151-
code = market.index[selection['selection']["rows"][0]]
152-
name = market.loc[code, "名称"]
153-
kline =fetch_kline(symbol=code)
154-
fig = sp.make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.8, 0.2])
155-
fig.add_trace(go.Candlestick(
156-
x=kline.index,
157-
open=kline.open,
158-
high=kline.high,
159-
low=kline.low,
160-
close=kline.close,
161-
name=name,
162-
), row=1, col=1)
163-
fig.add_trace(go.Bar(
164-
x=kline.index, y=kline.volume, name="volume"
165-
), row=2, col=1)
166-
st.plotly_chart(fig)
167-
168-
@st.dialog("Edit your strategy", width="large")
169-
def display_editor(code):
170-
height = max(len(code.split("\n")) * 20, 68)
171-
code = st.text_area(label="*edit your strategy*", value=code, height=height)
172-
if st.button("save", use_container_width=True):
173-
(st.session_state.strategy.__file__).write_text(code)
174-
st.rerun()
Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import sys
2-
import pandas as pd
31
import streamlit as st
4-
import quool.app as app
2+
import quool.app.tool as tool
53
from quool import Broker
6-
from streamlit import runtime
7-
from streamlit.web import cli
8-
from quool.app import (
4+
from .monitor import layout as monitor_layout
5+
from .performance import layout as performance_layout
6+
from .runner import layout as runner_layout
7+
from .strategy import layout as strategy_layout
8+
from .transact import layout as transact_layout
9+
from .tool import (
910
fetch_realtime,
1011
update_broker, update_market, update_strategy,
11-
ASSET_PATH, BROKER_PATH, LOG_PATH,
12-
TEMPLATE_PATH, STRATEGIES_PATH,
12+
ASSET_PATH, BROKER_PATH, LOG_PATH, STRATEGIES_PATH,
1313
)
1414

1515

1616
def setup_styler():
1717
styler = st.sidebar.text_input("*input code styler*", value="raw2ricequant")
18-
styler = getattr(app, styler, None)
18+
styler = getattr(tool, styler, None)
1919
if styler is None:
20-
st.sidebar.warning("No styler selected")
20+
st.sidebar.warning(f"No styler named: {styler.__name__}")
2121
else:
2222
st.session_state.styler = styler
2323

@@ -45,11 +45,11 @@ def setup_broker():
4545
st.rerun()
4646

4747
def setup_page():
48-
monitor = st.Page("monitor.py", title="Monitor", icon="📈")
49-
transact = st.Page("transact.py", title="Transact", icon="💸")
50-
runner = st.Page("runner.py", title="Runner", icon="▶️")
51-
performance = st.Page("performance.py", title="Performance", icon="📊")
52-
strategy = st.Page("strategy.py", title="Strategy", icon="💡")
48+
monitor = st.Page(monitor_layout, title="Monitor", icon="📈", url_path="monitor")
49+
transact = st.Page(transact_layout, title="Transact", icon="💸", url_path="transact")
50+
runner = st.Page(runner_layout, title="Runner", icon="▶️", url_path="runner")
51+
performance = st.Page(performance_layout, title="Performance", icon="📊", url_path="performance")
52+
strategy = st.Page(strategy_layout, title="Strategy", icon="💡", url_path="strategy")
5353
pg = st.navigation([monitor, transact, strategy, runner, performance])
5454
pg.run()
5555

@@ -59,7 +59,7 @@ def setup_market():
5959
st.session_state.timepoints = st.session_state.market.index.get_level_values(0).unique()
6060

6161
def setup_strategy():
62-
if st.session_state.broker is None:
62+
if st.session_state.get('broker') is None:
6363
st.sidebar.warning("No broker selected")
6464
return
6565

@@ -105,24 +105,15 @@ def setup_strategy():
105105
(STRATEGIES_PATH / f"{selection}.py").unlink()
106106
st.rerun()
107107

108-
def main():
108+
def layout():
109+
ASSET_PATH.mkdir(parents=True, exist_ok=True)
110+
BROKER_PATH.mkdir(parents=True, exist_ok=True)
111+
STRATEGIES_PATH.mkdir(parents=True, exist_ok=True)
112+
LOG_PATH.mkdir(parents=True, exist_ok=True)
109113
setup_styler()
110114
setup_broker()
111115
setup_market()
112116
setup_strategy()
113117
setup_page()
114118
with st.sidebar.container():
115119
update_broker()
116-
117-
118-
if __name__ == "__main__":
119-
ASSET_PATH.mkdir(parents=True, exist_ok=True)
120-
BROKER_PATH.mkdir(parents=True, exist_ok=True)
121-
STRATEGIES_PATH.mkdir(parents=True, exist_ok=True)
122-
TEMPLATE_PATH.mkdir(parents=True, exist_ok=True)
123-
LOG_PATH.mkdir(parents=True, exist_ok=True)
124-
if runtime.exists():
125-
main()
126-
else:
127-
sys.argv = ["streamlit", "run", __file__]
128-
cli.main()

quool/app/monitor.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pandas as pd
22
import streamlit as st
3-
from quool.app import BROKER_PATH, REFRESH_INTERVAL
3+
from .tool import BROKER_PATH, REFRESH_INTERVAL
44

55

66
@st.fragment(run_every=REFRESH_INTERVAL)
@@ -46,7 +46,3 @@ def layout():
4646
st.warning("No broker selected")
4747
return
4848
display_monitor(placeholder)
49-
50-
51-
if __name__ == "__page__":
52-
layout()

quool/app/performance.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import streamlit as st
33
import plotly.subplots as sp
44
import plotly.graph_objects as go
5-
from quool.app import read_market, fetch_realtime
5+
from .tool import read_market
66

77

88
def display_curve(values):
@@ -42,11 +42,17 @@ def display_evaluation(evaluation, trades):
4242
st.metric("Max Drawdown", f"{evaluation['max_drawdown(%)']:.2f}%")
4343
st.metric("Alpha", f"{evaluation['alpha(%)']:.2f}%")
4444
st.metric("Trade Win Rate", f"{evaluation['trade_win_rate(%)']:.2}%")
45-
st.metric("Position Duration", f"{evaluation['position_duration(days)'].days:.2f} days")
45+
st.metric("Position Duration",
46+
f"{evaluation['position_duration(days)'].days
47+
if isinstance(evaluation['position_duration(days)'], pd.Timedelta) else 0:.2f} days"
48+
)
4649
st.metric("Trade Return", f"{evaluation['trade_return(%)']:.2}%")
4750
with cols[1]:
4851
st.metric("Annual Return", f"{evaluation['annual_return(%)']:.2f}%")
49-
st.metric("Max Drawdown Period", f"{evaluation['max_drawdown_period'].days} days")
52+
st.metric("Max Drawdown Period", f"{
53+
evaluation['max_drawdown_period'].days
54+
if isinstance(evaluation['max_drawdown_period'], pd.Timedelta) else 0:
55+
} days")
5056
st.metric("Annual Volatility", f"{evaluation['annual_volatility(%)']:.2f}%")
5157
st.metric("Beta", f"{evaluation['beta']:.2f}")
5258
st.metric("Excess Return", f"{evaluation['excess_return(%)']:.2f}%")
@@ -80,7 +86,3 @@ def display_performance():
8086
def layout():
8187
st.title("PERFORMANCE")
8288
display_performance()
83-
84-
85-
if __name__ == "__page__":
86-
layout()

quool/app/runner.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import re
21
import pandas as pd
32
import streamlit as st
43
from io import BytesIO
54
from pathlib import Path
65
from joblib import Parallel, delayed
76
from quool import Broker, setup_logger
8-
from quool.app import (
9-
read_market, display_editor, update_strategy,
10-
STRATEGIES_PATH, LOG_PATH, BROKER_PATH, TEMPLATE_PATH, REFRESH_INTERVAL,
7+
from .tool import (
8+
read_market, update_strategy,
9+
STRATEGIES_PATH, LOG_PATH, BROKER_PATH
1110
)
1211

1312

@@ -121,12 +120,7 @@ def display_strategy():
121120
def layout():
122121
st.title("STRATEGIES RUNNER")
123122
display_market()
124-
display_selector()
125-
if st.button("edit", use_container_width=True):
126-
display_editor(Path(st.session_state.strategy.__file__).read_text())
123+
# display_selector()
124+
# if st.button("edit", use_container_width=True):
125+
# display_editor(Path(st.session_state.strategy.__file__).read_text())
127126
display_strategy()
128-
129-
130-
if __name__ == "__page__":
131-
layout()
132-

0 commit comments

Comments
 (0)