hyperquant 0.1.0__py3-none-any.whl
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.
- hyperquant/__init__.py +5 -0
- hyperquant/core.py +456 -0
- hyperquant/draw.py +1199 -0
- hyperquant/logkit.py +190 -0
- hyperquant-0.1.0.dist-info/METADATA +153 -0
- hyperquant-0.1.0.dist-info/RECORD +7 -0
- hyperquant-0.1.0.dist-info/WHEEL +4 -0
hyperquant/__init__.py
ADDED
hyperquant/core.py
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
# %%
|
2
|
+
import numpy as np
|
3
|
+
import pandas as pd
|
4
|
+
|
5
|
+
from draw import draw
|
6
|
+
|
7
|
+
|
8
|
+
class ExchangeBase:
|
9
|
+
def __init__(self, initial_balance=10000, recorded=False):
|
10
|
+
self.initial_balance = initial_balance # 初始的资产
|
11
|
+
self.recorded = recorded # 是否记录历史
|
12
|
+
self.opt = {
|
13
|
+
'trades': [],
|
14
|
+
'history': [] # 集成 history 到 opt 中
|
15
|
+
}
|
16
|
+
self.account = {'USDT': {'realised_profit': 0, 'unrealised_profit': 0, 'total': initial_balance,
|
17
|
+
'fee': 0, 'leverage': 0, 'hold': 0, 'long': 0, 'short': 0}}
|
18
|
+
|
19
|
+
def record_history(self, time):
|
20
|
+
"""记录当前总资产和时间到 history 中"""
|
21
|
+
self.opt['history'].append({
|
22
|
+
'date': time,
|
23
|
+
'total': self.account['USDT']['total']
|
24
|
+
})
|
25
|
+
|
26
|
+
def __getitem__(self, symbol):
|
27
|
+
return self.account.get(symbol, None)
|
28
|
+
|
29
|
+
def __setitem__(self, symbol, value):
|
30
|
+
self.account[symbol] = value
|
31
|
+
|
32
|
+
@property
|
33
|
+
def activate_symbols(self):
|
34
|
+
return [symbol for symbol in self.trade_symbols if self.account[symbol]['amount'] != 0]
|
35
|
+
|
36
|
+
@property
|
37
|
+
def total(self):
|
38
|
+
return self.account['USDT']['total']
|
39
|
+
|
40
|
+
@property
|
41
|
+
def leverage(self):
|
42
|
+
return self.account['USDT']['leverage']
|
43
|
+
|
44
|
+
@property
|
45
|
+
def realised_profit(self):
|
46
|
+
return self.account['USDT']['realised_profit']
|
47
|
+
|
48
|
+
@property
|
49
|
+
def unrealised_profit(self):
|
50
|
+
return self.account['USDT']['unrealised_profit']
|
51
|
+
|
52
|
+
@property
|
53
|
+
def history(self):
|
54
|
+
if not self.recorded:
|
55
|
+
raise ValueError("History is only available in recorded mode.")
|
56
|
+
return self.opt['history']
|
57
|
+
|
58
|
+
@property
|
59
|
+
def available_margin(self):
|
60
|
+
return self.account['USDT']['total'] - self.account['USDT']['hold']
|
61
|
+
|
62
|
+
@property
|
63
|
+
def realised_profit(self):
|
64
|
+
return self.account['USDT']['realised_profit']
|
65
|
+
|
66
|
+
@property
|
67
|
+
def trades(self):
|
68
|
+
return self.opt['trades']
|
69
|
+
|
70
|
+
@property
|
71
|
+
def stats(self):
|
72
|
+
if not self.recorded:
|
73
|
+
raise ValueError("Stats are only available in recorded mode.")
|
74
|
+
|
75
|
+
if not self.opt['history']:
|
76
|
+
return {
|
77
|
+
'初始资产': f'{self.initial_balance:.2f} USDT',
|
78
|
+
'当前资产': f'{self.account["USDT"]["total"]:.2f} USDT',
|
79
|
+
'已实现利润': f'{self.account["USDT"]["realised_profit"]:.2f} USDT',
|
80
|
+
'未实现利润': f'{self.account["USDT"]["unrealised_profit"]:.2f} USDT',
|
81
|
+
'总手续费': f'{self.account["USDT"]["fee"]:.2f} USDT',
|
82
|
+
'杯杆率': f'{self.account["USDT"]["leverage"]:.2f}',
|
83
|
+
'活跃交易对数量': len(self.activate_symbols),
|
84
|
+
'持仓价值': f'{self.account["USDT"]["hold"]:.2f} USDT',
|
85
|
+
'多头持仓价值': f'{self.account["USDT"]["long"]:.2f} USDT',
|
86
|
+
'空头持仓价值': f'{self.account["USDT"]["short"]:.2f} USDT',
|
87
|
+
'总交易笔数': 0,
|
88
|
+
'胜率': '0.00%',
|
89
|
+
'年化收益率': '0.00%',
|
90
|
+
'最大回撤时间范围': 'N/A',
|
91
|
+
'最大回撤': '0.00%',
|
92
|
+
'夏普比率': '0.00'
|
93
|
+
}
|
94
|
+
|
95
|
+
# 创建一个账户历史的DataFrame
|
96
|
+
history_df = pd.DataFrame(self.opt['history'])
|
97
|
+
history_df = history_df.sort_values(by='date')
|
98
|
+
history_df = history_df.drop_duplicates(subset='date')
|
99
|
+
history_df = history_df.set_index('date')
|
100
|
+
|
101
|
+
# 计算累计收益
|
102
|
+
history_df['max2here'] = history_df['total'].expanding().max()
|
103
|
+
history_df['dd2here'] = history_df['total'] / history_df['max2here'] - 1
|
104
|
+
drwa_down_df = history_df.sort_values(by=['dd2here'])
|
105
|
+
drwa_down_df = drwa_down_df[drwa_down_df['dd2here'] < 0]
|
106
|
+
if drwa_down_df.empty:
|
107
|
+
start_date = np.nan
|
108
|
+
end_data = np.nan
|
109
|
+
max_draw_down = 0
|
110
|
+
else:
|
111
|
+
max_draw_down = drwa_down_df.iloc[0]['dd2here']
|
112
|
+
end_data = drwa_down_df.iloc[0].name
|
113
|
+
start_date = history_df[history_df.index <= end_data].sort_values(by='total', ascending=False).iloc[0].name
|
114
|
+
|
115
|
+
# 计算胜率
|
116
|
+
total_trades = len(self.opt['trades'])
|
117
|
+
if total_trades == 0:
|
118
|
+
win_rate = 0
|
119
|
+
else:
|
120
|
+
winning_trades = sum(1 for trade in self.opt['trades'] if trade['pos'] > 0)
|
121
|
+
losing_trades = sum(1 for trade in self.opt['trades'] if trade['pos'] < 0)
|
122
|
+
win_rate = winning_trades / (winning_trades + losing_trades) if (winning_trades + losing_trades) > 0 else 0
|
123
|
+
|
124
|
+
# 计算年化收益率
|
125
|
+
if len(history_df) < 2:
|
126
|
+
annual_return = 0
|
127
|
+
else:
|
128
|
+
start_date_for_return = history_df.index[0]
|
129
|
+
end_date_for_return = history_df.index[-1]
|
130
|
+
total_days = (end_date_for_return - start_date_for_return).days
|
131
|
+
if total_days > 0:
|
132
|
+
annual_return = ((history_df['total'].iloc[-1] / self.initial_balance) ** (365 / total_days) - 1)
|
133
|
+
else:
|
134
|
+
annual_return = 0
|
135
|
+
|
136
|
+
# 计算夏普比率
|
137
|
+
# 计算每日收益率
|
138
|
+
daily_history = history_df['total'].resample('D').ffill().dropna()
|
139
|
+
daily_returns = daily_history.pct_change().dropna()
|
140
|
+
if len(daily_returns) > 1:
|
141
|
+
risk_free_rate = 0.03 / 365
|
142
|
+
sharpe_ratio = (daily_returns.mean() - risk_free_rate) / daily_returns.std() * np.sqrt(365)
|
143
|
+
else:
|
144
|
+
sharpe_ratio = 0
|
145
|
+
|
146
|
+
stats = {
|
147
|
+
'初始资产': f'{self.initial_balance:.2f} USDT',
|
148
|
+
'当前资产': f'{self.account["USDT"]["total"]:.2f} USDT',
|
149
|
+
'已实现利润': f'{self.account["USDT"]["realised_profit"]:.2f} USDT',
|
150
|
+
'未实现利润': f'{self.account["USDT"]["unrealised_profit"]:.2f} USDT',
|
151
|
+
'总手续费': f'{self.account["USDT"]["fee"]:.2f} USDT',
|
152
|
+
'活跃交易对数量': len(self.activate_symbols),
|
153
|
+
'持仓价值': f'{self.account["USDT"]["hold"]:.2f} USDT',
|
154
|
+
'多头持仓价值': f'{self.account["USDT"]["long"]:.2f} USDT',
|
155
|
+
'空头持仓价值': f'{self.account["USDT"]["short"]:.2f} USDT',
|
156
|
+
'总交易笔数': total_trades,
|
157
|
+
'胜率': f'{win_rate:.2%}',
|
158
|
+
'年化收益率': f'{annual_return:.2%}',
|
159
|
+
'最大回撤时间范围': (start_date,end_data),
|
160
|
+
'最大回撤': f'{max_draw_down:.2%}',
|
161
|
+
'夏普比率': f'{sharpe_ratio:.2f}'
|
162
|
+
}
|
163
|
+
return stats
|
164
|
+
|
165
|
+
def draw(self, data_df: pd.DataFrame, title: str, indicators: list, show_kline=True, show_total=True, show_base=False):
|
166
|
+
"""
|
167
|
+
:param data_df: 数据 DataFrame
|
168
|
+
:param title: 图表标题
|
169
|
+
:param indicators: 画图指标 [[('指标名', '指标类型'), ('指标名', '指标类型')], [('指标名', '指标类型')]]
|
170
|
+
:param show_kline: 是否显示K线图
|
171
|
+
:param show_total: 是否显示总资产曲线
|
172
|
+
"""
|
173
|
+
|
174
|
+
# 将 self.history 转换为 DataFrame
|
175
|
+
history_df = pd.DataFrame(self.opt['history'])
|
176
|
+
|
177
|
+
# 按照 'date' 分组,并保留每组的最后一条记录
|
178
|
+
history_df = history_df.sort_values('date').groupby('date', as_index=False).last()
|
179
|
+
|
180
|
+
# 按照 'date' 将 history_df 和 data_df 合并
|
181
|
+
data_df = pd.merge(data_df, history_df, on='date', how='left')
|
182
|
+
|
183
|
+
# 使用前向填充处理 'total' 列的缺失值
|
184
|
+
data_df['total'] = data_df['total'].ffill()
|
185
|
+
|
186
|
+
data_dict = []
|
187
|
+
if show_kline:
|
188
|
+
# 如果signal列存在,将signal列的值赋值给signal列
|
189
|
+
opt = {
|
190
|
+
"series_name": "K",
|
191
|
+
"draw_type": "Kline",
|
192
|
+
"col": ["open", "close", "low", "high"],
|
193
|
+
"height": 50,
|
194
|
+
}
|
195
|
+
if 'signal' in data_df.columns:
|
196
|
+
opt['trade_single'] = 'signal'
|
197
|
+
data_dict.append(opt)
|
198
|
+
|
199
|
+
|
200
|
+
if indicators:
|
201
|
+
for ind in indicators:
|
202
|
+
ind_data = {}
|
203
|
+
for i, indicator in enumerate(ind):
|
204
|
+
if i == 0:
|
205
|
+
ind_data = {
|
206
|
+
"series_name": indicator[0],
|
207
|
+
"draw_type": indicator[1],
|
208
|
+
"height": 0,
|
209
|
+
}
|
210
|
+
else:
|
211
|
+
if 'sub_chart' not in ind_data:
|
212
|
+
ind_data['sub_chart'] = []
|
213
|
+
ind_data['sub_chart'].append(
|
214
|
+
{"series_name": indicator[0], "draw_type": indicator[1]}
|
215
|
+
)
|
216
|
+
data_dict.append(ind_data)
|
217
|
+
|
218
|
+
if show_total:
|
219
|
+
# 绘制基准收益曲线
|
220
|
+
total_dict = {
|
221
|
+
"series_name": "total",
|
222
|
+
"draw_type": "Line",
|
223
|
+
"height": 0,
|
224
|
+
}
|
225
|
+
if show_base:
|
226
|
+
data_df.loc[:, "base"] = (data_df["close"] / data_df["close"].iloc[0]) * self.initial_balance
|
227
|
+
total_dict['sub_chart'] = [
|
228
|
+
{"series_name": "base", "draw_type": "Line"},
|
229
|
+
]
|
230
|
+
data_dict.append(total_dict)
|
231
|
+
|
232
|
+
|
233
|
+
sub_width = 40 / (len(data_dict) - 1)
|
234
|
+
for d in data_dict:
|
235
|
+
if d['draw_type'] != "Kline":
|
236
|
+
d['height'] = sub_width
|
237
|
+
|
238
|
+
draw(
|
239
|
+
data_df,
|
240
|
+
data_dict=data_dict,
|
241
|
+
date_col="date",
|
242
|
+
date_formate="%Y-%m-%d %H:%M:%S",
|
243
|
+
title=title,
|
244
|
+
height_type="%",
|
245
|
+
auto_play_space="""
|
246
|
+
function auto_play_space(xi){
|
247
|
+
return 200;
|
248
|
+
}""",
|
249
|
+
show=True,
|
250
|
+
display_js="""
|
251
|
+
// 设置 dataZoom 为最大范围
|
252
|
+
window.onload = function() {
|
253
|
+
var isSettingZoom = false;
|
254
|
+
|
255
|
+
// 获取 x 轴的数据
|
256
|
+
var xData = chart_option.xAxis[0].data;
|
257
|
+
if (xData.length > 0) {
|
258
|
+
var startValue = xData[0];
|
259
|
+
var endValue = xData[xData.length - 1];
|
260
|
+
isSettingZoom = true;
|
261
|
+
chart_ins.dispatchAction({
|
262
|
+
type: 'dataZoom',
|
263
|
+
startValue: startValue,
|
264
|
+
endValue: endValue
|
265
|
+
});
|
266
|
+
isSettingZoom = false;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
""",
|
270
|
+
)
|
271
|
+
|
272
|
+
class Exchange(ExchangeBase):
|
273
|
+
def __init__(self, trade_symbols, fee=0.0002, initial_balance=10000, recorded=False):
|
274
|
+
super().__init__(initial_balance=initial_balance, recorded=recorded)
|
275
|
+
self.fee = fee
|
276
|
+
self.trade_symbols = trade_symbols
|
277
|
+
self.id_gen = 0
|
278
|
+
self.account['USDT'].update({
|
279
|
+
'hold': 0,
|
280
|
+
'long': 0,
|
281
|
+
'short': 0
|
282
|
+
})
|
283
|
+
for symbol in trade_symbols:
|
284
|
+
self.account[symbol] = {'amount': 0, 'hold_price': 0, 'value': 0, 'price': 0,
|
285
|
+
'realised_profit': 0, 'unrealised_profit': 0, 'fee': 0}
|
286
|
+
|
287
|
+
def Trade(self, symbol, direction, price, amount, **kwargs):
|
288
|
+
if self.recorded and 'time' not in kwargs:
|
289
|
+
raise ValueError("Time parameter is required in recorded mode.")
|
290
|
+
|
291
|
+
time = kwargs.get('time', pd.Timestamp.now())
|
292
|
+
|
293
|
+
self.id_gen += 1
|
294
|
+
tid = len(self.trades) if self.recorded else self.id_gen
|
295
|
+
|
296
|
+
trade = {
|
297
|
+
'symbol': symbol,
|
298
|
+
'exchange': "local",
|
299
|
+
'orderid': tid,
|
300
|
+
'tradeid': tid,
|
301
|
+
'direction': direction,
|
302
|
+
'price': price,
|
303
|
+
'volume': abs(amount),
|
304
|
+
'datetime': time,
|
305
|
+
'gateway_name': "local",
|
306
|
+
'pos': 0 # 初始化盈亏
|
307
|
+
}
|
308
|
+
|
309
|
+
if symbol not in self.trade_symbols:
|
310
|
+
self.trade_symbols.append(symbol)
|
311
|
+
self.account[symbol] = {'amount': 0, 'hold_price': 0, 'value': 0, 'price': 0,
|
312
|
+
'realised_profit': 0, 'unrealised_profit': 0, 'fee': 0}
|
313
|
+
|
314
|
+
cover_amount = 0 if direction * self.account[symbol]['amount'] >= 0 else min(abs(self.account[symbol]['amount']), amount)
|
315
|
+
open_amount = amount - cover_amount
|
316
|
+
|
317
|
+
if cover_amount > 0 and np.isnan(price):
|
318
|
+
print(f'{symbol} 可能已经下架, 清仓')
|
319
|
+
price = self.account[symbol]['price'] if self.account[symbol]['price'] != 0 else self.account[symbol]['hold_price']
|
320
|
+
else:
|
321
|
+
if np.isnan(price) or np.isnan(amount):
|
322
|
+
print(f'{symbol} 价格或者数量为nan, 交易忽略')
|
323
|
+
return
|
324
|
+
|
325
|
+
# 扣除手续费
|
326
|
+
self.account['USDT']['realised_profit'] -= price * amount * self.fee
|
327
|
+
self.account['USDT']['fee'] += price * amount * self.fee
|
328
|
+
self.account[symbol]['fee'] += price * amount * self.fee
|
329
|
+
|
330
|
+
if cover_amount > 0: # 先平仓
|
331
|
+
profit = -direction * (price - self.account[symbol]['hold_price']) * cover_amount
|
332
|
+
self.account['USDT']['realised_profit'] += profit # 利润
|
333
|
+
self.account[symbol]['realised_profit'] += profit
|
334
|
+
self.account[symbol]['amount'] -= -direction * cover_amount
|
335
|
+
trade['pos'] = profit # 记录盈亏
|
336
|
+
self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
|
337
|
+
|
338
|
+
if open_amount > 0:
|
339
|
+
total_cost = self.account[symbol]['hold_price'] * direction * self.account[symbol]['amount'] + price * open_amount
|
340
|
+
total_amount = direction * self.account[symbol]['amount'] + open_amount
|
341
|
+
|
342
|
+
self.account[symbol]['hold_price'] = total_cost / total_amount
|
343
|
+
self.account[symbol]['amount'] += direction * open_amount
|
344
|
+
|
345
|
+
if kwargs:
|
346
|
+
self.opt.update(kwargs)
|
347
|
+
|
348
|
+
# 记录账户总资产到 history
|
349
|
+
if self.recorded:
|
350
|
+
self.opt['trades'].append(trade)
|
351
|
+
self.record_history(time)
|
352
|
+
|
353
|
+
# 自动更新账户状态
|
354
|
+
self.Update({symbol: price}, time=time)
|
355
|
+
|
356
|
+
return trade
|
357
|
+
|
358
|
+
def Buy(self, symbol, price, amount, **kwargs):
|
359
|
+
return self.Trade(symbol, 1, price, amount, **kwargs)
|
360
|
+
|
361
|
+
def Sell(self, symbol, price, amount, **kwargs):
|
362
|
+
return self.Trade(symbol, -1, price, amount, **kwargs)
|
363
|
+
|
364
|
+
def CloseAll(self, price, symbols=None, **kwargs):
|
365
|
+
if symbols is None:
|
366
|
+
symbols = self.trade_symbols
|
367
|
+
trades = []
|
368
|
+
symbols = [s for s in symbols if s in self.account and self.account[s]['amount'] != 0]
|
369
|
+
for symbol in symbols:
|
370
|
+
if symbol not in price or np.isnan(price[symbol]):
|
371
|
+
print(f'{symbol} 可能已经下架')
|
372
|
+
price[symbol] = self.account[symbol]['price'] if self.account[symbol]['price'] != 0 else self.account[symbol]['hold_price']
|
373
|
+
if np.isnan(price[symbol]):
|
374
|
+
price[symbol] = self.account[symbol]['price'] if self.account[symbol]['price'] != 0 else self.account[symbol]['hold_price']
|
375
|
+
|
376
|
+
direction = -np.sign(self.account[symbol]['amount'])
|
377
|
+
trade = self.Trade(symbol, direction, price[symbol], abs(self.account[symbol]['amount']), **kwargs)
|
378
|
+
trades.append(trade)
|
379
|
+
return trades
|
380
|
+
|
381
|
+
def Update(self, close_price, symbols=None, **kwargs):
|
382
|
+
if self.recorded and 'time' not in kwargs:
|
383
|
+
raise ValueError("Time parameter is required in recorded mode.")
|
384
|
+
|
385
|
+
time = kwargs.get('time', pd.Timestamp.now())
|
386
|
+
self.account['USDT']['unrealised_profit'] = 0
|
387
|
+
self.account['USDT']['hold'] = 0
|
388
|
+
self.account['USDT']['long'] = 0
|
389
|
+
self.account['USDT']['short'] = 0
|
390
|
+
if symbols is None:
|
391
|
+
# symbols = self.trade_symbols
|
392
|
+
# 如果symbols是dict类型, 则取出所有的key, 如果是Series类型, 则取出所有的index
|
393
|
+
if isinstance(close_price, dict):
|
394
|
+
symbols = list(close_price.keys())
|
395
|
+
elif isinstance(close_price, pd.Series):
|
396
|
+
symbols = close_price.index
|
397
|
+
else:
|
398
|
+
raise ValueError("Symbols should be a list, dict or Series.")
|
399
|
+
|
400
|
+
for symbol in symbols:
|
401
|
+
if symbol not in self.trade_symbols:
|
402
|
+
continue
|
403
|
+
if not np.isnan(close_price[symbol]):
|
404
|
+
self.account[symbol]['unrealised_profit'] = (close_price[symbol] - self.account[symbol]['hold_price']) * self.account[symbol]['amount']
|
405
|
+
self.account[symbol]['price'] = close_price[symbol]
|
406
|
+
self.account[symbol]['value'] = self.account[symbol]['amount'] * close_price[symbol]
|
407
|
+
if self.account[symbol]['amount'] > 0:
|
408
|
+
self.account['USDT']['long'] += self.account[symbol]['value']
|
409
|
+
if self.account[symbol]['amount'] < 0:
|
410
|
+
self.account['USDT']['short'] += self.account[symbol]['value']
|
411
|
+
self.account['USDT']['hold'] += abs(self.account[symbol]['value'])
|
412
|
+
self.account['USDT']['unrealised_profit'] += self.account[symbol]['unrealised_profit']
|
413
|
+
|
414
|
+
self.account['USDT']['total'] = round(self.account['USDT']['realised_profit'] + self.initial_balance + self.account['USDT']['unrealised_profit'], 6)
|
415
|
+
self.account['USDT']['leverage'] = round(self.account['USDT']['hold'] / self.account['USDT']['total'], 3)
|
416
|
+
|
417
|
+
# 记录账户总资产到 history
|
418
|
+
if self.recorded:
|
419
|
+
self.record_history(time)
|
420
|
+
|
421
|
+
# e = Exchange([])
|
422
|
+
# e.Sell('DOGEUSDT', 0.3, 3)
|
423
|
+
# print(e.account)
|
424
|
+
|
425
|
+
def gen_back_time(start_date, end_date, train_period_days, test_period_days):
|
426
|
+
# 将输入的日期字符串转换为时间戳
|
427
|
+
start_date = pd.to_datetime(start_date)
|
428
|
+
end_date = pd.to_datetime(end_date)
|
429
|
+
|
430
|
+
# 定义训练和测试周期
|
431
|
+
train_period = pd.Timedelta(days=train_period_days)
|
432
|
+
test_period = pd.Timedelta(days=test_period_days)
|
433
|
+
|
434
|
+
# 存储训练和测试日期区间
|
435
|
+
train_date = []
|
436
|
+
test_date = []
|
437
|
+
|
438
|
+
# 确定训练和测试的时间区间
|
439
|
+
current_date = start_date
|
440
|
+
while current_date + test_period <= end_date:
|
441
|
+
tsd_start = current_date
|
442
|
+
tsd_end = tsd_start + test_period
|
443
|
+
trd_end = tsd_start
|
444
|
+
trd_start = trd_end - train_period
|
445
|
+
|
446
|
+
train_date.append((pd.Timestamp(trd_start.date()), pd.Timestamp(trd_end.date())))
|
447
|
+
test_date.append((pd.Timestamp(tsd_start.date()), pd.Timestamp(tsd_end.date())))
|
448
|
+
|
449
|
+
# 移动到下一个测试周期的开始
|
450
|
+
current_date = tsd_end
|
451
|
+
|
452
|
+
# 将其转换为DataFrame
|
453
|
+
train_date = pd.DataFrame(train_date, columns=['x_start', 'x_end'])
|
454
|
+
test_date = pd.DataFrame(test_date, columns=['y_start', 'y_end'])
|
455
|
+
back_df = pd.concat([train_date, test_date], axis=1)
|
456
|
+
return back_df
|