请问这个功能加入到最新版的vnpy中了吗?
另外可以在本地储存手续费率和保证金率用于回测自动调用吗?
CTP回测GUI修改
注意原本使用Backtest已经可以正常使用回测
这里就是吃饱了撑着的把图形化也改了出来
注意事项同上
另外因为我没用米筐的数据,全是本地的,所以他的下载接口还没改也不会去改
C:\vnstudio\Lib\site-packages\vnpy\app\cta_backtester\ui
def init_ui(self):
""""""
self.setWindowTitle("CTA回测")
# Setting Part
self.class_combo = QtWidgets.QComboBox()
self.symbol_line = QtWidgets.QLineEdit("IF88.CFFEX")
self.collection_name_line = QtWidgets.QLineEdit("IF88")
self.interval_combo = QtWidgets.QComboBox()
for interval in Interval:
if interval != Interval.TICK:
self.interval_combo.addItem(interval.value)
end_dt = datetime.now()
start_dt = end_dt - timedelta(days=3 * 365)
self.start_date_edit = QtWidgets.QDateEdit(
QtCore.QDate(
start_dt.year,
start_dt.month,
start_dt.day
)
)
self.end_date_edit = QtWidgets.QDateEdit(
QtCore.QDate.currentDate()
)
self.rate_line = QtWidgets.QLineEdit("0.000025")
self.slippage_line = QtWidgets.QLineEdit("0.2")
self.size_line = QtWidgets.QLineEdit("300")
self.pricetick_line = QtWidgets.QLineEdit("0.2")
self.capital_line = QtWidgets.QLineEdit("1000000")
self.inverse_combo = QtWidgets.QComboBox()
self.inverse_combo.addItems(["正向", "反向"])
backtesting_button = QtWidgets.QPushButton("开始回测")
backtesting_button.clicked.connect(self.start_backtesting)
optimization_button = QtWidgets.QPushButton("参数优化")
optimization_button.clicked.connect(self.start_optimization)
self.result_button = QtWidgets.QPushButton("优化结果")
self.result_button.clicked.connect(self.show_optimization_result)
self.result_button.setEnabled(False)
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)
self.candle_button = QtWidgets.QPushButton("K线图表")
self.candle_button.clicked.connect(self.show_candle_chart)
self.candle_button.setEnabled(False)
edit_button = QtWidgets.QPushButton("代码编辑")
edit_button.clicked.connect(self.edit_strategy_code)
reload_button = QtWidgets.QPushButton("策略重载")
reload_button.clicked.connect(self.reload_strategy_class)
for button in [
backtesting_button,
optimization_button,
downloading_button,
self.result_button,
self.order_button,
self.trade_button,
self.daily_button,
self.candle_button,
edit_button,
reload_button
]:
button.setFixedHeight(button.sizeHint().height() * 2)
form = QtWidgets.QFormLayout()
form.addRow("交易策略", self.class_combo)
form.addRow("本地代码", self.symbol_line)
form.addRow("OrderBookId", self.collection_name_line)
form.addRow("K线周期", self.interval_combo)
form.addRow("开始日期", self.start_date_edit)
form.addRow("结束日期", self.end_date_edit)
form.addRow("手续费率", self.rate_line)
form.addRow("交易滑点", self.slippage_line)
form.addRow("合约乘数", self.size_line)
form.addRow("价格跳动", self.pricetick_line)
form.addRow("回测资金", self.capital_line)
form.addRow("合约模式", self.inverse_combo)
result_grid = QtWidgets.QGridLayout()
result_grid.addWidget(self.trade_button, 0, 0)
result_grid.addWidget(self.order_button, 0, 1)
result_grid.addWidget(self.daily_button, 1, 0)
result_grid.addWidget(self.candle_button, 1, 1)
left_vbox = QtWidgets.QVBoxLayout()
left_vbox.addLayout(form)
left_vbox.addWidget(backtesting_button)
left_vbox.addWidget(downloading_button)
left_vbox.addStretch()
left_vbox.addLayout(result_grid)
left_vbox.addStretch()
left_vbox.addWidget(optimization_button)
left_vbox.addWidget(self.result_button)
left_vbox.addStretch()
left_vbox.addWidget(edit_button)
left_vbox.addWidget(reload_button)
# Result part
self.statistics_monitor = StatisticsMonitor()
self.log_monitor = QtWidgets.QTextEdit()
self.log_monitor.setMaximumHeight(400)
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
)
# Candle Chart
self.candle_dialog = CandleChartDialog()
# Layout
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.statistics_monitor)
vbox.addWidget(self.log_monitor)
hbox = QtWidgets.QHBoxLayout()
hbox.addLayout(left_vbox)
hbox.addLayout(vbox)
hbox.addWidget(self.chart)
self.setLayout(hbox)
# Code Editor
self.editor = CodeEditor(self.main_engine, self.event_engine)
def load_backtesting_setting(self):
""""""
setting = load_json(self.setting_filename)
if not setting:
return
self.class_combo.setCurrentIndex(
self.class_combo.findText(setting["class_name"])
)
self.symbol_line.setText(setting["vt_symbol"])
self.collection_name_line.setText(setting["collection_name"])
self.interval_combo.setCurrentIndex(
self.interval_combo.findText(setting["interval"])
)
self.rate_line.setText(str(setting["rate"]))
self.slippage_line.setText(str(setting["slippage"]))
self.size_line.setText(str(setting["size"]))
self.pricetick_line.setText(str(setting["pricetick"]))
self.capital_line.setText(str(setting["capital"]))
if not setting["inverse"]:
self.inverse_combo.setCurrentIndex(0)
else:
self.inverse_combo.setCurrentIndex(1)
def start_backtesting(self):
""""""
class_name = self.class_combo.currentText()
vt_symbol = self.symbol_line.text()
collection_name = self.collection_name_line.text()
interval = self.interval_combo.currentText()
start = self.start_date_edit.date().toPyDate()
end = self.end_date_edit.date().toPyDate()
rate = float(self.rate_line.text())
slippage = float(self.slippage_line.text())
size = float(self.size_line.text())
pricetick = float(self.pricetick_line.text())
capital = float(self.capital_line.text())
if self.inverse_combo.currentText() == "正向":
inverse = False
else:
inverse = True
# Save backtesting parameters
backtesting_setting = {
"class_name": class_name,
"vt_symbol": vt_symbol,
"collection_name":collection_name,
"interval": interval,
"rate": rate,
"slippage": slippage,
"size": size,
"pricetick": pricetick,
"capital": capital,
"inverse": inverse,
}
save_json(self.setting_filename, backtesting_setting)
# Get strategy setting
old_setting = self.settings[class_name]
dialog = BacktestingSettingEditor(class_name, old_setting)
i = dialog.exec()
if i != dialog.Accepted:
return
new_setting = dialog.get_setting()
self.settings[class_name] = new_setting
result = self.backtester_engine.start_backtesting(
class_name,
vt_symbol,
collection_name,
interval,
start,
end,
rate,
slippage,
size,
pricetick,
capital,
inverse,
new_setting
)
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.candle_button.setEnabled(False)
self.trade_dialog.clear_data()
self.order_dialog.clear_data()
self.daily_dialog.clear_data()
self.candle_dialog.clear_data()
def start_optimization(self):
""""""
class_name = self.class_combo.currentText()
vt_symbol = self.symbol_line.text()
collection_name=self.collection_name_line.text()
interval = self.interval_combo.currentText()
start = self.start_date_edit.date().toPyDate()
end = self.end_date_edit.date().toPyDate()
rate = float(self.rate_line.text())
slippage = float(self.slippage_line.text())
size = float(self.size_line.text())
pricetick = float(self.pricetick_line.text())
capital = float(self.capital_line.text())
if self.inverse_combo.currentText() == "正向":
inverse = False
else:
inverse = True
parameters = self.settings[class_name]
dialog = OptimizationSettingEditor(class_name, parameters)
i = dialog.exec()
if i != dialog.Accepted:
return
optimization_setting, use_ga = dialog.get_setting()
self.target_display = dialog.target_display
self.backtester_engine.start_optimization(
class_name,
vt_symbol,
collection_name,
interval,
start,
end,
rate,
slippage,
size,
pricetick,
capital,
inverse,
optimization_setting,
use_ga
)
self.result_button.setEnabled(False)
def start_downloading(self):
""""""
vt_symbol = self.symbol_line.text()
collection_name=self.collection_name_line.text()
interval = self.interval_combo.currentText()
start_date = self.start_date_edit.date()
end_date = self.end_date_edit.date()
start = datetime(
start_date.year(),
start_date.month(),
start_date.day(),
tzinfo=get_localzone()
)
end = datetime(
end_date.year(),
end_date.month(),
end_date.day(),
23,
59,
59,
tzinfo=get_localzone()
)
self.backtester_engine.start_downloading(
vt_symbol,
interval,
start,
end
)
C:\vnstudio\Lib\site-packages\vnpy\app\cta_backtester
def run_backtesting(
self,
class_name: str,
vt_symbol: str,
collection_name:str,
interval: str,
start: datetime,
end: datetime,
rate: float,
slippage: float,
size: int,
pricetick: float,
capital: int,
inverse: bool,
setting: dict,
):
""""""
self.result_df = None
self.result_statistics = None
engine = self.backtesting_engine
engine.clear_data()
engine.set_parameters(
vt_symbol=vt_symbol,
interval=interval,
start=start,
end=end,
rate=rate,
slippage=slippage,
size=size,
pricetick=pricetick,
capital=capital,
inverse=inverse,
collection_name=collection_name
)
strategy_class = self.classes[class_name]
engine.add_strategy(
strategy_class,
setting
)
engine.load_data()
try:
engine.run_backtesting()
except Exception:
msg = f"策略回测失败,触发异常:\n{traceback.format_exc()}"
self.write_log(msg)
self.thread = None
return
self.result_df = engine.calculate_result()
self.result_statistics = engine.calculate_statistics(output=False)
# Clear thread object handler.
self.thread = None
# Put backtesting done event
event = Event(EVENT_BACKTESTER_BACKTESTING_FINISHED)
self.event_engine.put(event)
def start_backtesting(
self,
class_name: str,
vt_symbol: str,
collection_name:str,
interval: str,
start: datetime,
end: datetime,
rate: float,
slippage: float,
size: int,
pricetick: float,
capital: int,
inverse: bool,
setting: dict
):
if self.thread:
self.write_log("已有任务在运行中,请等待完成")
return False
self.write_log("-" * 40)
self.thread = Thread(
target=self.run_backtesting,
args=(
class_name,
vt_symbol,
collection_name,
interval,
start,
end,
rate,
slippage,
size,
pricetick,
capital,
inverse,
setting
)
)
self.thread.start()
return True
改完就又能在GUI用分表的MongoDB了
挺好
10小时了,一个回复都没有,甚至只有寥寥无几的浏览,在求助板块发的帖子还被删除了。过于失望
下面是解决方案。果然还是只能自己动手丰衣足食。
为了简化工作量分表的名字都是合约的代号 如'SR2011',同时只考虑了BarData,即除了'db_tick_data'都视为BarData
默认安装位置,修改了自行推导
C:\vnstudio\Lib\site-packages\vnpy\trader\database\database.py
@abstractmethod
def get_newest_bar_data(
self,
symbol: str,
exchange: "Exchange",
interval: "Interval",
collection_name: str = None
) -> Optional["BarData"]:
"""
If there is data in database, return the one with greatest datetime(newest one)
otherwise, return None
"""
pass
@abstractmethod
def get_oldest_bar_data(
self,
symbol: str,
exchange: "Exchange",
interval: "Interval",
collection_name: str = None
) -> Optional["BarData"]:
"""
If there is data in database, return the one with smallest datetime(oldest one)
otherwise, return None
"""
pass
@abstractmethod
def get_newest_tick_data(
self,
symbol: str,
exchange: "Exchange",
collection_name: str = None
) -> Optional["TickData"]:
"""
If there is data in database, return the one with greatest datetime(newest one)
otherwise, return None
"""
pass
@abstractmethod
def get_bar_data_statistics(
self,
symbol: str,
exchange: "Exchange",
collection_name: str = None
) -> List[Dict]:
"""
Return data avaible in database with a list of symbol/exchange/interval/count.
"""
pass
@abstractmethod
def delete_bar_data(
self,
symbol: str,
exchange: "Exchange",
interval: "Interval",
collection_name: str = None
) -> int:
"""
Delete all bar data with given symbol + exchange + interval.
"""
pass
C:\vnstudio\Lib\site-packages\vnpy\trader\database\database_mongo.py
def get_newest_bar_data(
self, symbol: str, exchange: "Exchange", interval: "Interval",collection_name:str=None
) -> Optional["BarData"]:
if collection_name is None:
s=(
DbBarData.objects(
symbol=symbol,
exchange=exchange.value,
interval=interval.value
)
.order_by("-datetime")
.first()
)
else:
with switch_collection(DbBarData, collection_name):
s = (
DbBarData.objects(
symbol=symbol,
exchange=exchange.value,
interval=interval.value
)
.order_by("-datetime")
.first()
)
if s:
return s.to_bar()
return None
def get_oldest_bar_data(
self, symbol: str, exchange: "Exchange", interval: "Interval",collection_name:str=None
) -> Optional["BarData"]:
if collection_name is None:
s = (
DbBarData.objects(
symbol=symbol,
exchange=exchange.value,
interval=interval.value
)
.order_by("+datetime")
.first()
)
else:
with switch_collection(DbBarData, collection_name):
s = (
DbBarData.objects(
symbol=symbol,
exchange=exchange.value,
interval=interval.value
)
.order_by("+datetime")
.first()
)
if s:
return s.to_bar()
return None
def get_newest_tick_data(
self, symbol: str, exchange: "Exchange",collection_name:str=None
) -> Optional["TickData"]:
if collection_name is None:
s = (
DbTickData.objects(symbol=symbol, exchange=exchange.value)
.order_by("-datetime")
.first()
)
else:
with switch_collection(DbTickData, collection_name):
s = (
DbTickData.objects(symbol=symbol, exchange=exchange.value)
.order_by("-datetime")
.first()
)
if s:
return s.to_tick()
return None
def get_bar_data_statistics(self) -> List:
""""""
if 'db_tick_data' in AllCollectionNames:
AllCollectionNames.remove('db_tick_data')
## print(AllCollectionNames)
s=list()
for i in AllCollectionNames:
with switch_collection(DbBarData,i):
s.extend(
DbBarData.objects.aggregate({
"$group": {
"_id": {
"symbol": "$symbol",
"exchange": "$exchange",
"interval": "$interval",
"collection_name":i,
},
"count": {"$sum": 1}
}
}))
result = []
for d in s:
data = d["_id"]
data["count"] = d["count"]
result.append(data)
return result
def delete_bar_data(
self,
symbol: str,
exchange: "Exchange",
interval: "Interval",
collection_name:str=None
) -> int:
"""
Delete all bar data with given symbol + exchange + interval.
"""
if collection_name is None:
count = DbBarData.objects(
symbol=symbol,
exchange=exchange.value,
interval=interval.value
).delete()
else:
with switch_collection(DbBarData, collection_name):
count = DbBarData.objects(
symbol=symbol,
exchange=exchange.value,
interval=interval.value
).delete()
return count
C:\vnstudio\Lib\site-packages\vnpy\app\data_manager\engine.py
def import_data_from_csv(
self,
file_path: str,
symbol: str,
exchange: Exchange,
interval: Interval,
datetime_head: str,
open_head: str,
high_head: str,
low_head: str,
close_head: str,
volume_head: str,
open_interest_head: str,
datetime_format: str,
OrderBookId:str
) -> Tuple:
""""""
with open(file_path, "rt") as f:
buf = [line.replace("\0", "") for line in f]
reader = csv.DictReader(buf, delimiter=",")
bars = []
start = None
count = 0
for item in reader:
if datetime_format:
dt = datetime.strptime(item[datetime_head], datetime_format)
else:
dt = datetime.fromisoformat(item[datetime_head])
open_interest = item.get(open_interest_head, 0)
bar = BarData(
symbol=symbol,
exchange=exchange,
datetime=dt,
interval=interval,
volume=float(item[volume_head]),
open_price=float(item[open_head]),
high_price=float(item[high_head]),
low_price=float(item[low_head]),
close_price=float(item[close_head]),
open_interest=float(open_interest),
gateway_name="DB",
)
bars.append(bar)
# do some statistics
count += 1
if not start:
start = bar.datetime
# insert into database
database_manager.save_bar_data(bars,OrderBookId)
end = bar.datetime
return start, end, count
def get_bar_data_available(self) -> List[Dict]:
""""""
data = database_manager.get_bar_data_statistics()
for d in data:
oldest_bar = database_manager.get_oldest_bar_data(
d["symbol"], Exchange(d["exchange"]), Interval(d["interval"]),
d['collection_name']
)
d["start"] = oldest_bar.datetime
newest_bar = database_manager.get_newest_bar_data(
d["symbol"], Exchange(d["exchange"]), Interval(d["interval"]),
d['collection_name']
)
d["end"] = newest_bar.datetime
return data
def load_bar_data(
self,
symbol: str,
exchange: Exchange,
interval: Interval,
start: datetime,
end: datetime,
collection_name: str = None,
) -> List[BarData]:
""""""
bars = database_manager.load_bar_data(
symbol,
exchange,
interval,
start,
end,
symbol,
)
return bars
def delete_bar_data(
self,
symbol: str,
exchange: Exchange,
interval: Interval,
collection_name: str = None,
) -> int:
""""""
count = database_manager.delete_bar_data(
symbol,
exchange,
interval,
symbol,
)
return count
C:\vnstudio\Lib\site-packages\vnpy\app\data_manager\ui\widget.py
def import_data(self) -> None:
""""""
dialog = ImportDialog()
n = dialog.exec_()
if n != dialog.Accepted:
return
file_path = dialog.file_edit.text()
symbol = dialog.symbol_edit.text()
OrderBookId= dialog.OrderBookId_edit.text()
exchange = dialog.exchange_combo.currentData()
interval = dialog.interval_combo.currentData()
datetime_head = dialog.datetime_edit.text()
open_head = dialog.open_edit.text()
low_head = dialog.low_edit.text()
high_head = dialog.high_edit.text()
close_head = dialog.close_edit.text()
volume_head = dialog.volume_edit.text()
open_interest_head = dialog.open_interest_edit.text()
datetime_format = dialog.format_edit.text()
start, end, count = self.engine.import_data_from_csv(
file_path,
symbol,
exchange,
interval,
datetime_head,
open_head,
high_head,
low_head,
close_head,
volume_head,
open_interest_head,
datetime_format,
OrderBookId
)
msg = f"\
CSV载入成功\n\
代码:{symbol}\n\
OrderBookId:{OrderBookId}\n\
交易所:{exchange.value}\n\
周期:{interval.value}\n\
起始:{start}\n\
结束:{end}\n\
总数量:{count}\n\
"
QtWidgets.QMessageBox.information(self, "载入成功!", msg)
def __init__(self, parent=None):
""""""
super().__init__()
self.setWindowTitle("从CSV文件导入数据")
self.setFixedWidth(300)
self.setWindowFlags(
(self.windowFlags() | QtCore.Qt.CustomizeWindowHint)
& ~QtCore.Qt.WindowMaximizeButtonHint)
file_button = QtWidgets.QPushButton("选择文件")
file_button.clicked.connect(self.select_file)
load_button = QtWidgets.QPushButton("确定")
load_button.clicked.connect(self.accept)
self.file_edit = QtWidgets.QLineEdit()
self.symbol_edit = QtWidgets.QLineEdit()
self.OrderBookId_edit = QtWidgets.QLineEdit()
self.exchange_combo = QtWidgets.QComboBox()
for i in Exchange:
self.exchange_combo.addItem(str(i.name), i)
self.interval_combo = QtWidgets.QComboBox()
for i in Interval:
if i != Interval.TICK:
self.interval_combo.addItem(str(i.name), i)
self.datetime_edit = QtWidgets.QLineEdit("datetime")
self.open_edit = QtWidgets.QLineEdit("open")
self.high_edit = QtWidgets.QLineEdit("high")
self.low_edit = QtWidgets.QLineEdit("low")
self.close_edit = QtWidgets.QLineEdit("close")
self.volume_edit = QtWidgets.QLineEdit("volume")
self.open_interest_edit = QtWidgets.QLineEdit("open_interest")
self.format_edit = QtWidgets.QLineEdit("%Y-%m-%d %H:%M:%S")
info_label = QtWidgets.QLabel("合约信息")
info_label.setAlignment(QtCore.Qt.AlignCenter)
head_label = QtWidgets.QLabel("表头信息")
head_label.setAlignment(QtCore.Qt.AlignCenter)
format_label = QtWidgets.QLabel("格式信息")
format_label.setAlignment(QtCore.Qt.AlignCenter)
form = QtWidgets.QFormLayout()
form.addRow(file_button, self.file_edit)
form.addRow(QtWidgets.QLabel())
form.addRow(info_label)
form.addRow("代码", self.symbol_edit)
form.addRow("OrderBookId", self.OrderBookId_edit)
form.addRow("交易所", self.exchange_combo)
form.addRow("周期", self.interval_combo)
form.addRow(QtWidgets.QLabel())
form.addRow(head_label)
form.addRow("时间戳", self.datetime_edit)
form.addRow("开盘价", self.open_edit)
form.addRow("最高价", self.high_edit)
form.addRow("最低价", self.low_edit)
form.addRow("收盘价", self.close_edit)
form.addRow("成交量", self.volume_edit)
form.addRow("持仓量", self.open_interest_edit)
form.addRow(QtWidgets.QLabel())
form.addRow(format_label)
form.addRow("时间格式", self.format_edit)
form.addRow(QtWidgets.QLabel())
form.addRow(load_button)
self.setLayout(form)
非常感谢社区精选18 19的作者,我这个操作完全是在他(们)的思路上继续做的,不然我也想不到
另外vnpy写的很赞,虽然没有多少注释,但是变量名把所有的逻辑解释的明明白白,很强
还是希望社区内能互帮互助吧,相逢即是缘,交易已经这么不容易了。
已正确配置MongoDB,在MongoDBCompass内可正常查看数据库内容。
根据社区精选18 19 修改了对应文件,地址分别是https://zhuanlan.zhihu.com/p/135939575 和https://zhuanlan.zhihu.com/p/135940704
但是在数据管理中,刷新数据后没有任何返回
由于采用集中在db_bar_data中时能正常识别,推测数据管理模块没有识别到其他表格中的内容,需要对对应文件做相应修改。同时可以推测,行情录制模块需要做类似修改以加入已有表格中。
想请教社区里面的各位大神,这里应该对哪些文件做什么修改呢?还请不吝赐教,感谢~