commit
6311bb510e
156
docs/_static/custom.css
vendored
Normal file
156
docs/_static/custom.css
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
@import url("vendor.css");
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Margin bottom by footer height */
|
||||
margin-bottom: 60px;
|
||||
/*padding-top: 50px; */ /* uncomment if navbar should be fixed */
|
||||
}
|
||||
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: "Lato","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 42px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 300;
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0 0 10px 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
.page-sidebar {
|
||||
margin-top: 25px;
|
||||
border: 1px solid #cad7e1;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.search > input[type="text"] {
|
||||
width: 165px;
|
||||
}
|
||||
|
||||
/* dirty but works ¯\_(ツ)_/¯ */
|
||||
@media (max-width:1200px) and (min-width:768px) {
|
||||
.search > input[type="text"] {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebarwrapper {
|
||||
padding: 10px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.page-sidebarwrapper ul li a {
|
||||
display: block;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
text-decoration: underline;
|
||||
border: none;
|
||||
}
|
||||
|
||||
a.reference:active {
|
||||
text-decoration: underline;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 16px rgba(85, 85, 85, 0.8);
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding-top: 0px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.paper {
|
||||
background-color: #FFF;
|
||||
padding: 20px;
|
||||
border: 1px solid #cad7e1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.paper:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
border-width: 0 40px 40px 0;
|
||||
border-style: solid;
|
||||
border-color: #ccc #F6F9FC;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
/* Set the fixed height of the footer here */
|
||||
height: 60px;
|
||||
padding: 20px 0;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.copyright,
|
||||
.made-in,
|
||||
.powered-by {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin: 0 0 12px;
|
||||
line-height: 1.42857;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #cad7e1;
|
||||
border-radius: 0;
|
||||
}
|
9
docs/_static/vendor.css
vendored
Normal file
9
docs/_static/vendor.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/_static/vnpy.ico
vendored
Normal file
BIN
docs/_static/vnpy.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
140
docs/_templates/layout.html
vendored
Normal file
140
docs/_templates/layout.html
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
{% extends 'basic/layout.html' %}
|
||||
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" href="{{ pathto('_static/custom.css', 1) }}" type="text/css" />
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
{% if theme_canonical_url %}
|
||||
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
|
||||
{% endif %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
||||
{% endblock %}
|
||||
|
||||
{# Disable base theme's top+bottom related navs; we have our own in sidebar #}
|
||||
{%- block relbar1 %}{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
|
||||
{# Nav should appear before content, not after #}
|
||||
{%- block content %}
|
||||
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
|
||||
<div class="container topnav">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#topnav-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand topnav" href="https://www.vnpy.com/">vn.py — By Traders, For Traders</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="topnav-collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<a href="/">主页</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/forum/">社区</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/">文档</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.github.com/vnpy/vnpy" target="_blank">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div><!-- /.navbar-collapse -->
|
||||
</div><!-- /.container -->
|
||||
</nav>
|
||||
|
||||
{%- macro sidebar() %}
|
||||
{%- if render_sidebar %}
|
||||
<div class="page-sidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="page-sidebarwrapper">
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}"><img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/></a>
|
||||
</p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- if sidebars != None %}
|
||||
{#- new style sidebar: explicitly include/exclude templates #}
|
||||
{%- for sidebartemplate in sidebars %}
|
||||
{%- include sidebartemplate %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- if theme_fixed_sidebar|lower == 'true' %}
|
||||
<div class="container">
|
||||
{% if render_sidebar %}
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-3">
|
||||
{{ sidebar() }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- block document %}
|
||||
|
||||
<div class="col-xs-12 col-sm-8 col-md-8 col-lg-9 page-content" role="main">
|
||||
<div class="paper">
|
||||
{% block body %} {% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{%- else %}
|
||||
{{ super() }}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block footer %}
|
||||
<footer class="flaskbb-footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
||||
<p class="copyright text-muted small pull-left">
|
||||
© 2015 -
|
||||
<!--<script type="text/javascript">document.write(new Date().getFullYear());</script>-->
|
||||
2019
|
||||
|
||||
vn.py Team
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
||||
<p class="made-in text-muted small" style="text-align: center">
|
||||
By Traders, For Traders
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
||||
<p class="powered-by text-muted small pull-right">
|
||||
powered by <a href="https://flaskbb.org">FlaskBB</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
{% if theme_analytics_id %}
|
||||
<script type="text/javascript">
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '{{ theme_analytics_id }}']);
|
||||
_gaq.push(['_setDomainName', 'none']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
{%- endblock %}
|
22
docs/_templates/relations.html
vendored
Normal file
22
docs/_templates/relations.html
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{# Changes to the original: capitalized 'Documentation Overview' #}
|
||||
<div class="relations">
|
||||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation Overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
||||
</div>
|
13
docs/_templates/searchbox.html
vendored
Normal file
13
docs/_templates/searchbox.html
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{# Changes to the original: capitalized 'Quick Search' #}
|
||||
{%- if pagename != "search" and builder != "singlehtml" %}
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<h3>{{ _('Quick Search') }}</h3>
|
||||
<form class="search" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" />
|
||||
<input type="submit" value="{{ _('Go') }}" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/javascript">$('#searchbox').show(0);</script>
|
||||
{%- endif %}
|
6
docs/_templates/sidebarintro.html
vendored
Normal file
6
docs/_templates/sidebarintro.html
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://flaskbb.org">FlaskBB Website</a></li>
|
||||
<li><a href="https://forums.flaskbb.org">FlaskBB Forums</a></li>
|
||||
<li><a href="https://github.com/sh4nks/flaskbb">FlaskBB @ GitHub</a></li>
|
||||
</ul>
|
50
docs/conf.py
50
docs/conf.py
@ -24,9 +24,9 @@ copyright = '2019, vn.py Team'
|
||||
author = 'vn.py Team'
|
||||
|
||||
# The short X.Y version
|
||||
version = '2.0'
|
||||
version = '2.0.3'
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '2.0-DEV'
|
||||
release = '2.0.3'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@ -38,7 +38,9 @@ release = '2.0-DEV'
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'recommonmark'
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'recommonmark',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
@ -72,7 +74,7 @@ locale_dirs = ["locale/"]
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
@ -85,8 +87,20 @@ html_theme = 'alabaster'
|
||||
# documentation.
|
||||
#
|
||||
html_theme_options = {
|
||||
'logo': 'vnpy.ico',
|
||||
"base_bg": "inherit",
|
||||
"narrow_sidebar_bg": "inherit",
|
||||
'github_banner': True,
|
||||
'github_user': 'vnpy',
|
||||
'github_repo': 'vnpy',
|
||||
'github_type': 'star',
|
||||
'description': (r"<div class='col-md-12'>"
|
||||
r"<strong>VN.PY</strong>"
|
||||
r"</div>"
|
||||
r"<br/>"
|
||||
r"By Traders, For Traders"),
|
||||
'fixed_sidebar': True,
|
||||
'show_related': True
|
||||
}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
@ -102,7 +116,21 @@ html_static_path = ['_static']
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
html_sidebars = {
|
||||
'index': [
|
||||
'about.html',
|
||||
'sidebarintro.html',
|
||||
'sourcelink.html',
|
||||
'searchbox.html'
|
||||
],
|
||||
'**': [
|
||||
'about.html',
|
||||
'localtoc.html',
|
||||
'relations.html',
|
||||
'sourcelink.html',
|
||||
'searchbox.html'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
@ -158,6 +186,11 @@ texinfo_documents = [
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# -- Options for intersphinx output ----------------------------------------------
|
||||
intersphinx_mapping = {
|
||||
#'python': ('https://docs.python.org/3/', None),
|
||||
}
|
||||
|
||||
# -- Options for Epub output -------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
@ -171,6 +204,11 @@ epub_title = project
|
||||
# A unique identification for the text.
|
||||
#
|
||||
# epub_uid = ''
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
# -- Options for Autodoc -------------------------------------------------
|
||||
autodoc_default_options = {
|
||||
'member-order': 'bysource',
|
||||
'undoc-members': True,
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
Widget for algo trading.
|
||||
"""
|
||||
|
||||
import csv
|
||||
from functools import partial
|
||||
from datetime import datetime
|
||||
|
||||
@ -68,6 +69,12 @@ class AlgoWidget(QtWidgets.QWidget):
|
||||
start_algo_button.clicked.connect(self.start_algo)
|
||||
form.addRow(start_algo_button)
|
||||
|
||||
load_csv_button = QtWidgets.QPushButton("CSV启动")
|
||||
load_csv_button.clicked.connect(self.load_csv)
|
||||
form.addRow(load_csv_button)
|
||||
|
||||
form.addRow(QtWidgets.QLabel(""))
|
||||
form.addRow(QtWidgets.QLabel(""))
|
||||
form.addRow(QtWidgets.QLabel(""))
|
||||
|
||||
self.setting_name_line = QtWidgets.QLineEdit()
|
||||
@ -77,8 +84,79 @@ class AlgoWidget(QtWidgets.QWidget):
|
||||
save_setting_button.clicked.connect(self.save_setting)
|
||||
form.addRow(save_setting_button)
|
||||
|
||||
for button in [
|
||||
start_algo_button,
|
||||
load_csv_button,
|
||||
save_setting_button
|
||||
]:
|
||||
button.setFixedHeight(button.sizeHint().height() * 2)
|
||||
|
||||
self.setLayout(form)
|
||||
|
||||
def load_csv(self):
|
||||
""""""
|
||||
# Get csv file path from dialog
|
||||
path, type_ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self,
|
||||
u"加载算法配置",
|
||||
"",
|
||||
"CSV(*.csv)"
|
||||
)
|
||||
|
||||
if not path:
|
||||
return
|
||||
|
||||
# Create csv DictReader
|
||||
with open(path, "r") as f:
|
||||
buf = [line for line in f]
|
||||
reader = csv.DictReader(buf)
|
||||
|
||||
# Check whether the csv file has all fields
|
||||
for field_name in self.widgets.keys():
|
||||
if field_name not in reader.fieldnames:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"字段缺失",
|
||||
f"CSV文件缺失算法{self.template_name}所需字段{field_name}"
|
||||
)
|
||||
return
|
||||
|
||||
# Read setting
|
||||
settings = []
|
||||
|
||||
for d in reader:
|
||||
# Initialize algo setting with template name
|
||||
setting = {
|
||||
"template_name": self.template_name
|
||||
}
|
||||
|
||||
# Read field from each dict of csv line
|
||||
for field_name, tp in self.widgets.items():
|
||||
field_type = tp[-1]
|
||||
field_text = d[field_name]
|
||||
|
||||
if field_type == list:
|
||||
field_value = field_text
|
||||
else:
|
||||
try:
|
||||
field_value = field_type(field_text)
|
||||
except ValueError:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"参数错误",
|
||||
f"{field_name}参数类型应为{field_type},请检查!"
|
||||
)
|
||||
return
|
||||
|
||||
setting[field_name] = field_value
|
||||
|
||||
# Add setting into list
|
||||
settings.append(setting)
|
||||
|
||||
# Only start algos if no exception/error occured
|
||||
for setting in settings:
|
||||
self.algo_engine.start_algo(setting)
|
||||
|
||||
def get_setting(self):
|
||||
"""
|
||||
Get setting value from line edits.
|
||||
|
@ -339,7 +339,7 @@ class BacktesterEngine(BaseEngine):
|
||||
self.write_log(f"{vt_symbol}-{interval}开始下载历史数据")
|
||||
|
||||
symbol, exchange = extract_vt_symbol(vt_symbol)
|
||||
|
||||
|
||||
req = HistoryRequest(
|
||||
symbol=symbol,
|
||||
exchange=exchange,
|
||||
@ -390,3 +390,15 @@ class BacktesterEngine(BaseEngine):
|
||||
self.thread.start()
|
||||
|
||||
return True
|
||||
|
||||
def get_all_trades(self):
|
||||
""""""
|
||||
return self.backtesting_engine.get_all_trades()
|
||||
|
||||
def get_all_orders(self):
|
||||
""""""
|
||||
return self.backtesting_engine.get_all_orders()
|
||||
|
||||
def get_all_daily_results(self):
|
||||
""""""
|
||||
return self.backtesting_engine.get_all_daily_results()
|
||||
|
@ -12,6 +12,7 @@ from ..engine import (
|
||||
from vnpy.trader.constant import Interval
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.ui import QtCore, QtWidgets, QtGui
|
||||
from vnpy.trader.ui.widget import BaseMonitor, BaseCell, DirectionCell, EnumCell
|
||||
from vnpy.event import Event, EventEngine
|
||||
|
||||
|
||||
@ -95,11 +96,26 @@ class BacktesterManager(QtWidgets.QWidget):
|
||||
downloading_button = QtWidgets.QPushButton("下载数据")
|
||||
downloading_button.clicked.connect(self.start_downloading)
|
||||
|
||||
self.order_button = QtWidgets.QPushButton("委托记录")
|
||||
self.order_button.clicked.connect(self.show_backtesting_orders)
|
||||
self.order_button.setEnabled(False)
|
||||
|
||||
self.trade_button = QtWidgets.QPushButton("成交记录")
|
||||
self.trade_button.clicked.connect(self.show_backtesting_trades)
|
||||
self.trade_button.setEnabled(False)
|
||||
|
||||
self.daily_button = QtWidgets.QPushButton("每日盈亏")
|
||||
self.daily_button.clicked.connect(self.show_daily_results)
|
||||
self.daily_button.setEnabled(False)
|
||||
|
||||
for button in [
|
||||
backtesting_button,
|
||||
optimization_button,
|
||||
downloading_button,
|
||||
self.result_button
|
||||
self.result_button,
|
||||
self.order_button,
|
||||
self.trade_button,
|
||||
self.daily_button
|
||||
]:
|
||||
button.setFixedHeight(button.sizeHint().height() * 2)
|
||||
|
||||
@ -114,12 +130,16 @@ class BacktesterManager(QtWidgets.QWidget):
|
||||
form.addRow("合约乘数", self.size_line)
|
||||
form.addRow("价格跳动", self.pricetick_line)
|
||||
form.addRow("回测资金", self.capital_line)
|
||||
form.addRow(backtesting_button)
|
||||
|
||||
|
||||
left_vbox = QtWidgets.QVBoxLayout()
|
||||
left_vbox.addLayout(form)
|
||||
left_vbox.addWidget(backtesting_button)
|
||||
left_vbox.addWidget(downloading_button)
|
||||
left_vbox.addStretch()
|
||||
left_vbox.addWidget(self.trade_button)
|
||||
left_vbox.addWidget(self.order_button)
|
||||
left_vbox.addWidget(self.daily_button)
|
||||
left_vbox.addStretch()
|
||||
left_vbox.addWidget(optimization_button)
|
||||
left_vbox.addWidget(self.result_button)
|
||||
|
||||
@ -132,6 +152,25 @@ class BacktesterManager(QtWidgets.QWidget):
|
||||
self.chart = BacktesterChart()
|
||||
self.chart.setMinimumWidth(1000)
|
||||
|
||||
self.trade_dialog = BacktestingResultDialog(
|
||||
self.main_engine,
|
||||
self.event_engine,
|
||||
"回测成交记录",
|
||||
BacktestingTradeMonitor
|
||||
)
|
||||
self.order_dialog = BacktestingResultDialog(
|
||||
self.main_engine,
|
||||
self.event_engine,
|
||||
"回测委托记录",
|
||||
BacktestingOrderMonitor
|
||||
)
|
||||
self.daily_dialog = BacktestingResultDialog(
|
||||
self.main_engine,
|
||||
self.event_engine,
|
||||
"回测每日盈亏",
|
||||
DailyResultMonitor
|
||||
)
|
||||
|
||||
# Layout
|
||||
vbox = QtWidgets.QVBoxLayout()
|
||||
vbox.addWidget(self.statistics_monitor)
|
||||
@ -176,6 +215,10 @@ class BacktesterManager(QtWidgets.QWidget):
|
||||
df = self.backtester_engine.get_result_df()
|
||||
self.chart.set_data(df)
|
||||
|
||||
self.trade_button.setEnabled(True)
|
||||
self.order_button.setEnabled(True)
|
||||
self.daily_button.setEnabled(True)
|
||||
|
||||
def process_optimization_finished_event(self, event: Event):
|
||||
""""""
|
||||
self.write_log("请点击[优化结果]按钮查看")
|
||||
@ -220,6 +263,14 @@ class BacktesterManager(QtWidgets.QWidget):
|
||||
if result:
|
||||
self.statistics_monitor.clear_data()
|
||||
self.chart.clear_data()
|
||||
|
||||
self.trade_button.setEnabled(False)
|
||||
self.order_button.setEnabled(False)
|
||||
self.daily_button.setEnabled(False)
|
||||
|
||||
self.trade_dialog.clear_data()
|
||||
self.order_dialog.clear_data()
|
||||
self.daily_dialog.clear_data()
|
||||
|
||||
def start_optimization(self):
|
||||
""""""
|
||||
@ -284,6 +335,30 @@ class BacktesterManager(QtWidgets.QWidget):
|
||||
)
|
||||
dialog.exec_()
|
||||
|
||||
def show_backtesting_trades(self):
|
||||
""""""
|
||||
if not self.trade_dialog.is_updated():
|
||||
trades = self.backtester_engine.get_all_trades()
|
||||
self.trade_dialog.update_data(trades)
|
||||
|
||||
self.trade_dialog.exec_()
|
||||
|
||||
def show_backtesting_orders(self):
|
||||
""""""
|
||||
if not self.order_dialog.is_updated():
|
||||
orders = self.backtester_engine.get_all_orders()
|
||||
self.order_dialog.update_data(orders)
|
||||
|
||||
self.order_dialog.exec_()
|
||||
|
||||
def show_daily_results(self):
|
||||
""""""
|
||||
if not self.daily_dialog.is_updated():
|
||||
results = self.backtester_engine.get_all_daily_results()
|
||||
self.daily_dialog.update_data(results)
|
||||
|
||||
self.daily_dialog.exec_()
|
||||
|
||||
def show(self):
|
||||
""""""
|
||||
self.showMaximized()
|
||||
@ -750,3 +825,117 @@ class OptimizationResultMonitor(QtWidgets.QDialog):
|
||||
vbox.addWidget(table)
|
||||
|
||||
self.setLayout(vbox)
|
||||
|
||||
|
||||
class BacktestingTradeMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for backtesting trade data.
|
||||
"""
|
||||
|
||||
headers = {
|
||||
"tradeid": {"display": "成交号 ", "cell": BaseCell, "update": False},
|
||||
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
|
||||
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
|
||||
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
|
||||
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
|
||||
"offset": {"display": "开平", "cell": EnumCell, "update": False},
|
||||
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
||||
"volume": {"display": "数量", "cell": BaseCell, "update": False},
|
||||
"datetime": {"display": "时间", "cell": BaseCell, "update": False},
|
||||
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
|
||||
}
|
||||
|
||||
|
||||
class BacktestingOrderMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for backtesting order data.
|
||||
"""
|
||||
|
||||
headers = {
|
||||
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
|
||||
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
|
||||
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
|
||||
"type": {"display": "类型", "cell": EnumCell, "update": False},
|
||||
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
|
||||
"offset": {"display": "开平", "cell": EnumCell, "update": False},
|
||||
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
||||
"volume": {"display": "总数量", "cell": BaseCell, "update": False},
|
||||
"traded": {"display": "已成交", "cell": BaseCell, "update": False},
|
||||
"status": {"display": "状态", "cell": EnumCell, "update": False},
|
||||
"datetime": {"display": "时间", "cell": BaseCell, "update": False},
|
||||
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
|
||||
}
|
||||
|
||||
|
||||
class DailyResultMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for backtesting daily result.
|
||||
"""
|
||||
|
||||
headers = {
|
||||
"date": {"display": "日期", "cell": BaseCell, "update": False},
|
||||
"trade_count": {"display": "成交笔数", "cell": BaseCell, "update": False},
|
||||
"start_pos": {"display": "开盘持仓", "cell": BaseCell, "update": False},
|
||||
"end_pos": {"display": "收盘持仓", "cell": BaseCell, "update": False},
|
||||
"turnover": {"display": "成交额", "cell": BaseCell, "update": False},
|
||||
"commission": {"display": "手续费", "cell": BaseCell, "update": False},
|
||||
"slippage": {"display": "滑点", "cell": BaseCell, "update": False},
|
||||
"trading_pnl": {"display": "交易盈亏", "cell": BaseCell, "update": False},
|
||||
"holding_pnl": {"display": "持仓盈亏", "cell": BaseCell, "update": False},
|
||||
"total_pnl": {"display": "总盈亏", "cell": BaseCell, "update": False},
|
||||
"net_pnl": {"display": "净盈亏", "cell": BaseCell, "update": False},
|
||||
}
|
||||
|
||||
|
||||
class BacktestingResultDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
main_engine: MainEngine,
|
||||
event_engine: EventEngine,
|
||||
title: str,
|
||||
table_class: QtWidgets.QTableWidget
|
||||
):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.main_engine = main_engine
|
||||
self.event_engine = event_engine
|
||||
self.title = title
|
||||
self.table_class = table_class
|
||||
|
||||
self.updated = False
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.setWindowTitle(self.title)
|
||||
self.resize(1100, 600)
|
||||
|
||||
self.table = self.table_class(self.main_engine, self.event_engine)
|
||||
|
||||
vbox = QtWidgets.QVBoxLayout()
|
||||
vbox.addWidget(self.table)
|
||||
|
||||
self.setLayout(vbox)
|
||||
|
||||
def clear_data(self):
|
||||
""""""
|
||||
self.updated = False
|
||||
self.table.setRowCount(0)
|
||||
|
||||
def update_data(self, data: list):
|
||||
""""""
|
||||
self.updated = True
|
||||
|
||||
data.reverse()
|
||||
for obj in data:
|
||||
self.table.insert_new_row(obj)
|
||||
|
||||
def is_updated(self):
|
||||
""""""
|
||||
return self.updated
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Callable
|
||||
from itertools import product
|
||||
from functools import lru_cache
|
||||
@ -211,22 +211,44 @@ class BacktestingEngine:
|
||||
""""""
|
||||
self.output("开始加载历史数据")
|
||||
|
||||
if self.mode == BacktestingMode.BAR:
|
||||
self.history_data = load_bar_data(
|
||||
self.symbol,
|
||||
self.exchange,
|
||||
self.interval,
|
||||
self.start,
|
||||
self.end
|
||||
)
|
||||
else:
|
||||
self.history_data = load_tick_data(
|
||||
self.symbol,
|
||||
self.exchange,
|
||||
self.start,
|
||||
self.end
|
||||
)
|
||||
if not self.end:
|
||||
self.end = datetime.now()
|
||||
|
||||
# Load 30 days of data each time and allow for progress update
|
||||
progress_delta = timedelta(days=30)
|
||||
total_delta = self.end - self.start
|
||||
|
||||
start = self.start
|
||||
end = self.start + progress_delta
|
||||
progress = 0
|
||||
|
||||
while start < self.end:
|
||||
if self.mode == BacktestingMode.BAR:
|
||||
data = load_bar_data(
|
||||
self.symbol,
|
||||
self.exchange,
|
||||
self.interval,
|
||||
start,
|
||||
end
|
||||
)
|
||||
else:
|
||||
data = load_tick_data(
|
||||
self.symbol,
|
||||
self.exchange,
|
||||
start,
|
||||
end
|
||||
)
|
||||
|
||||
self.history_data.extend(data)
|
||||
|
||||
progress += progress_delta / total_delta
|
||||
progress = min(progress, 1)
|
||||
progress_bar = "#" * int(progress * 10)
|
||||
self.output(f"加载进度:{progress_bar} [{progress:.0%}]")
|
||||
|
||||
start = end
|
||||
end += progress_delta
|
||||
|
||||
self.output(f"历史数据加载完成,数据量:{len(self.history_data)}")
|
||||
|
||||
def run_backtesting(self):
|
||||
@ -803,6 +825,7 @@ class BacktestingEngine:
|
||||
status=Status.ALLTRADED,
|
||||
gateway_name=self.gateway_name,
|
||||
)
|
||||
order.datetime = self.datetime
|
||||
|
||||
self.limit_orders[order.vt_orderid] = order
|
||||
|
||||
@ -921,6 +944,7 @@ class BacktestingEngine:
|
||||
status=Status.NOTTRADED,
|
||||
gateway_name=self.gateway_name,
|
||||
)
|
||||
order.datetime = self.datetime
|
||||
|
||||
self.active_limit_orders[order.vt_orderid] = order
|
||||
self.limit_orders[order.vt_orderid] = order
|
||||
@ -997,6 +1021,23 @@ class BacktestingEngine:
|
||||
"""
|
||||
print(f"{datetime.now()}\t{msg}")
|
||||
|
||||
def get_all_trades(self):
|
||||
"""
|
||||
Return all trade data of current backtesting result.
|
||||
"""
|
||||
return list(self.trades.values())
|
||||
|
||||
def get_all_orders(self):
|
||||
"""
|
||||
Return all limit order data of current backtesting result.
|
||||
"""
|
||||
return list(self.limit_orders.values())
|
||||
|
||||
def get_all_daily_results(self):
|
||||
"""
|
||||
Return all daily result data.
|
||||
"""
|
||||
return list(self.daily_results.values())
|
||||
|
||||
class DailyResult:
|
||||
""""""
|
||||
|
@ -64,7 +64,7 @@ class OnetokenGateway(BaseGateway):
|
||||
default_setting = {
|
||||
"OT Key": "",
|
||||
"OT Secret": "",
|
||||
"交易所": ["BINANCE", "BITMEX", "OKEX", "OKEF"],
|
||||
"交易所": ["BINANCE", "BITMEX", "OKEX", "OKEF", "HUOBIP", "HUOBIF"],
|
||||
"账户": "",
|
||||
"会话数": 3,
|
||||
"代理地址": "127.0.0.1",
|
||||
|
@ -19,7 +19,7 @@ from tigeropen.tiger_open_config import TigerOpenClientConfig
|
||||
from tigeropen.common.consts import Language, Currency, Market
|
||||
from tigeropen.quote.quote_client import QuoteClient
|
||||
from tigeropen.trade.trade_client import TradeClient
|
||||
from tigeropen.trade.domain.order import ORDER_STATUS
|
||||
from tigeropen.trade.domain.order import OrderStatus
|
||||
from tigeropen.push.push_client import PushClient
|
||||
from tigeropen.common.exceptions import ApiException
|
||||
|
||||
@ -64,15 +64,15 @@ ORDERTYPE_VT2TIGER = {
|
||||
}
|
||||
|
||||
STATUS_TIGER2VT = {
|
||||
ORDER_STATUS.PENDING_NEW: Status.SUBMITTING,
|
||||
ORDER_STATUS.NEW: Status.SUBMITTING,
|
||||
ORDER_STATUS.HELD: Status.SUBMITTING,
|
||||
ORDER_STATUS.PARTIALLY_FILLED: Status.PARTTRADED,
|
||||
ORDER_STATUS.FILLED: Status.ALLTRADED,
|
||||
ORDER_STATUS.CANCELLED: Status.CANCELLED,
|
||||
ORDER_STATUS.PENDING_CANCEL: Status.CANCELLED,
|
||||
ORDER_STATUS.REJECTED: Status.REJECTED,
|
||||
ORDER_STATUS.EXPIRED: Status.NOTTRADED
|
||||
OrderStatus.PENDING_NEW: Status.SUBMITTING,
|
||||
OrderStatus.NEW: Status.SUBMITTING,
|
||||
OrderStatus.HELD: Status.SUBMITTING,
|
||||
OrderStatus.PARTIALLY_FILLED: Status.PARTTRADED,
|
||||
OrderStatus.FILLED: Status.ALLTRADED,
|
||||
OrderStatus.CANCELLED: Status.CANCELLED,
|
||||
OrderStatus.PENDING_CANCEL: Status.CANCELLED,
|
||||
OrderStatus.REJECTED: Status.REJECTED,
|
||||
OrderStatus.EXPIRED: Status.NOTTRADED
|
||||
}
|
||||
|
||||
PUSH_STATUS_TIGER2VT = {
|
||||
@ -559,7 +559,7 @@ class TigerGateway(BaseGateway):
|
||||
Process trade data for both query and update.
|
||||
"""
|
||||
for i in data:
|
||||
if i.status == ORDER_STATUS.PARTIALLY_FILLED or i.status == ORDER_STATUS.FILLED:
|
||||
if i.status == OrderStatus.PARTIALLY_FILLED or i.status == OrderStatus.FILLED:
|
||||
symbol, exchange = convert_symbol_tiger2vt(str(i.contract))
|
||||
self.tradeid += 1
|
||||
|
||||
|
@ -237,8 +237,9 @@ class BaseMonitor(QtWidgets.QTableWidget):
|
||||
"""
|
||||
Register event handler into event engine.
|
||||
"""
|
||||
self.signal.connect(self.process_event)
|
||||
self.event_engine.register(self.event_type, self.signal.emit)
|
||||
if self.event_type:
|
||||
self.signal.connect(self.process_event)
|
||||
self.event_engine.register(self.event_type, self.signal.emit)
|
||||
|
||||
def process_event(self, event):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user