1oscon commited on
Commit
c1735b3
·
verified ·
1 Parent(s): 2c0494e

Update 配置.py

Browse files
Files changed (1) hide show
  1. 配置.py +141 -75
配置.py CHANGED
@@ -6,6 +6,7 @@ from datetime import datetime, timezone
6
  默认保存根 = "/tmp/app_data"
7
  os.makedirs(默认保存根, exist_ok=True)
8
 
 
9
  def _guess_mapping(headers):
10
  m = {"时间": None, "开": None, "高": None, "低": None, "收": None, "量": None}
11
  cand = {
@@ -31,13 +32,11 @@ def _parse_time_to_ms(s):
31
  s = str(s).strip()
32
  if not s:
33
  return None
34
- # 数字:秒或毫秒
35
  if re.fullmatch(r"\d{10,13}", s):
36
  v = int(s)
37
- if v < 10**12: # 秒
38
  return v * 1000
39
  return v
40
- # ISO 格式
41
  try:
42
  ss = s.replace("Z", "+00:00")
43
  dt = datetime.fromisoformat(ss)
@@ -46,14 +45,7 @@ def _parse_time_to_ms(s):
46
  return int(dt.timestamp() * 1000)
47
  except Exception:
48
  pass
49
- # 常见格式补刀
50
- fmts = [
51
- "%Y-%m-%d %H:%M:%S",
52
- "%Y/%m/%d %H:%M:%S",
53
- "%Y-%m-%d %H:%M",
54
- "%Y/%m/%d %H:%M",
55
- "%Y-%m-%dT%H:%M:%S",
56
- ]
57
  for f in fmts:
58
  try:
59
  dt = datetime.strptime(s, f).replace(tzinfo=timezone.utc)
@@ -68,7 +60,6 @@ def _sniff_delimiter(path):
68
  try:
69
  return csv.Sniffer().sniff(sample).delimiter
70
  except Exception:
71
- # 兜底
72
  return ',' if sample.count(',') >= sample.count('\t') else '\t'
73
 
74
  def 读取CSV预览(file_obj):
@@ -87,7 +78,7 @@ def 读取CSV预览(file_obj):
87
  if i < 5:
88
  head_rows.append(row)
89
  total += 1
90
- info = f"分隔符: '{delim}' | 列: {len(headers)} | 行(不含表头): ~{total}"
91
  preview = "\n".join([",".join(row) for row in head_rows])
92
  return info, ",".join(headers), preview
93
  except Exception as e:
@@ -103,12 +94,10 @@ def 标准化CSV(file_obj, 输出文件名, 时间列, 开列, 高列, 低列,
103
  os.makedirs(save_dir, exist_ok=True)
104
  out_path = os.path.join(save_dir, 输出文件名)
105
 
106
- # 校验必要列
107
  必要 = [时间列, 开列, 高列, 低列, 收列]
108
  if not all(必要):
109
- return f"请确保时间收列已选择", ""
110
-
111
- # 读取、转换并写出标准格式
112
  delim = _sniff_delimiter(path)
113
  写入 = 0
114
  try:
@@ -116,14 +105,11 @@ def 标准化CSV(file_obj, 输出文件名, 时间列, 开列, 高列, 低列,
116
  open(out_path, "w", encoding="utf-8", newline="") as w:
117
  r = csv.DictReader(f, delimiter=delim)
118
  wr = csv.writer(w)
119
- wr.writerow(["时间","开","高","低","收","量"]) # 标准表头
120
  for row in r:
121
  try:
122
  tms = _parse_time_to_ms(row[时间列])
123
- o = float(row[开列])
124
- h = float(row[高列])
125
- l = float(row[低列])
126
- c = float(row[收列])
127
  v = float(row[量列]) if 量列 and 量列 in row and row[量列] not in (None, "") else 0.0
128
  if tms is None:
129
  continue
@@ -135,6 +121,56 @@ def 标准化CSV(file_obj, 输出文件名, 时间列, 开列, 高列, 低列,
135
  except Exception as e:
136
  return f"保存失败: {e}", ""
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  def 加载参数文件(param_file):
139
  if param_file is None:
140
  return json.dumps({"ver":"v1"}, ensure_ascii=False, indent=2)
@@ -169,11 +205,8 @@ def 保存参数(JSON文本, 文件名):
169
  except Exception as e:
170
  return f"保存失败: {e}"
171
 
 
172
  def 构建设置面板(容器, 默认参数=None, 记录函数=None):
173
- """
174
- 在给定容器内构建设置面板。
175
- 返回一个字典,包含用于回填到运行页的组件(参数编辑框、复制按钮)。
176
- """
177
  if 默认参数 is None:
178
  默认参数 = {"ver":"v1"}
179
 
@@ -182,12 +215,8 @@ def 构建设置面板(容器, 默认参数=None, 记录函数=None):
182
  with gr.TabItem("参数配置"):
183
  with gr.Row():
184
  with gr.Column(scale=1):
185
- gr.Markdown("### 参数编辑")
186
- 参数编辑框 = gr.Textbox(
187
- label="策略参数(JSON可编辑)",
188
- value=json.dumps(默认参数, ensure_ascii=False, indent=2),
189
- lines=18
190
- )
191
  with gr.Row():
192
  参数文件 = gr.File(label="上传参数JSON文件", file_types=[".json"])
193
  加载按钮 = gr.Button("加载到编辑框")
@@ -197,13 +226,12 @@ def 构建设置面板(容器, 默认参数=None, 记录函数=None):
197
  参数保存名 = gr.Textbox(label="保存文件名", value="params.json")
198
  参数保存按钮 = gr.Button("保存参数到磁盘")
199
  保存结果 = gr.Textbox(label="保存结果", interactive=False)
200
- 复制按钮 = gr.Button("将上方参数回填到运行页", variant="primary")
201
 
202
  with gr.Column(scale=1):
203
  gr.Markdown("小提示")
204
  gr.Markdown("- 可上传回测生成的最优参数JSON\n- 编辑后点“格式化/校验”再保存\n- 点击“将上方参数回填到运行页”即可带回主界面")
205
 
206
- # 事件
207
  加载按钮.click(加载参数文件, inputs=参数文件, outputs=参数编辑框)
208
  格式化按钮.click(格式化参数, inputs=参数编辑框, outputs=[参数编辑框, 加载结果])
209
  参数保存按钮.click(保存参数, inputs=[参数编辑框, 参数保存名], outputs=保存结果)
@@ -211,74 +239,107 @@ def 构建设置面板(容器, 默认参数=None, 记录函数=None):
211
  with gr.TabItem("CSV上传"):
212
  with gr.Row():
213
  with gr.Column(scale=1):
 
214
  csv文件 = gr.File(label="上传 1m K线 CSV", file_types=[".csv"])
215
-
216
- # 预览区域
217
  预览信息 = gr.Textbox(label="文件信息", lines=2, interactive=False)
218
  表头显示 = gr.Textbox(label="检测到的表头", lines=2, interactive=False)
219
  预览前几行 = gr.Textbox(label="前几行示例", lines=8, interactive=False)
220
-
221
- # 映射下拉菜单(静态创建)
222
- gr.Markdown("### 列映射")
223
  时间列 = gr.Dropdown(label="时间列", choices=[], value=None)
224
  开列 = gr.Dropdown(label="开盘价列", choices=[], value=None)
225
  高列 = gr.Dropdown(label="最高价列", choices=[], value=None)
226
  低列 = gr.Dropdown(label="最低价列", choices=[], value=None)
227
  收列 = gr.Dropdown(label="收盘价列", choices=[], value=None)
228
  量列 = gr.Dropdown(label="成交量列(可选)", choices=[], value=None)
229
-
230
- 输出文件名 = gr.Textbox(label="输出文件名", value="uploaded_1m.csv")
231
- 保存按钮 = gr.Button("标准化并保存")
 
 
 
 
232
  保存结果 = gr.Textbox(label="结果", interactive=False)
233
  输出路径 = gr.Textbox(label="输出路径", interactive=False)
234
-
 
 
 
 
 
 
 
 
235
  with gr.Column(scale=1):
236
- gr.Markdown("### 使用说明")
237
- gr.Markdown("1. 上传 CSV 文件\n2. 系统自动检测表头\n3. 选择对应的列映射\n4. 点击保存生成标准格式")
238
- gr.Markdown("标准格式: 时间(ms), 开, 高, 低, 收, 量")
239
 
240
- # CSV 上传后的处理函数
241
  def 处理CSV上传(file_obj):
242
- if file_obj is None:
243
- return ("", "", "", [], None, None, None, None, None, None)
244
-
245
  info, headers_str, preview = 读取CSV预览(file_obj)
246
  if not headers_str:
247
- return (info, "", preview, [], None, None, None, None, None, None)
248
-
249
  headers = headers_str.split(",")
250
  映射 = _guess_mapping(headers)
251
-
252
  return (
253
- info,
254
- headers_str,
255
- preview,
256
- headers,
257
- 映射.get("时间"),
258
- 映射.get(""),
259
- 映射.get(""),
260
- 映射.get("低"),
261
- 映射.get("收"),
262
- 映射.get("量")
263
  )
264
 
265
- # 更新下拉菜单选项和默认值
266
  csv文件.change(
267
  fn=处理CSV上传,
268
  inputs=csv文件,
269
- outputs=[
270
- 预览信息, 表头显示, 预览前几行,
271
- 时间列, 时间列, 开列, 高列, 低列, 收列, 量列
272
- ]
273
  )
274
-
275
- # 保存按钮点击事件
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  保存按钮.click(
277
- fn=标准化CSV,
278
- inputs=[csv文件, 输出文件名, 时间列, 开列, 高列, 低列, 收列, 量列],
279
- outputs=[保存结果, 输出路径]
280
  )
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  with gr.TabItem("系统"):
283
  修复按钮 = gr.Button("修复导入路径/别名(重试)")
284
  修复结果 = gr.Textbox(label="结果", interactive=False)
@@ -302,5 +363,10 @@ def 构建设置面板(容器, 默认参数=None, 记录函数=None):
302
  return f"失败:{e}"
303
  修复按钮.click(do_fix, outputs=修复结果)
304
 
305
- # 返回用于回填运行页关键组件
306
- return {"参数编辑框": 参数编辑框, "复制按钮": 复制按钮}
 
 
 
 
 
 
6
  默认保存根 = "/tmp/app_data"
7
  os.makedirs(默认保存根, exist_ok=True)
8
 
9
+ # ---------- 工具 ----------
10
  def _guess_mapping(headers):
11
  m = {"时间": None, "开": None, "高": None, "低": None, "收": None, "量": None}
12
  cand = {
 
32
  s = str(s).strip()
33
  if not s:
34
  return None
 
35
  if re.fullmatch(r"\d{10,13}", s):
36
  v = int(s)
37
+ if v < 10**12:
38
  return v * 1000
39
  return v
 
40
  try:
41
  ss = s.replace("Z", "+00:00")
42
  dt = datetime.fromisoformat(ss)
 
45
  return int(dt.timestamp() * 1000)
46
  except Exception:
47
  pass
48
+ fmts = ["%Y-%m-%d %H:%M:%S","%Y/%m/%d %H:%M:%S","%Y-%m-%d %H:%M","%Y/%m/%d %H:%M","%Y-%m-%dT%H:%M:%S"]
 
 
 
 
 
 
 
49
  for f in fmts:
50
  try:
51
  dt = datetime.strptime(s, f).replace(tzinfo=timezone.utc)
 
60
  try:
61
  return csv.Sniffer().sniff(sample).delimiter
62
  except Exception:
 
63
  return ',' if sample.count(',') >= sample.count('\t') else '\t'
64
 
65
  def 读取CSV预览(file_obj):
 
78
  if i < 5:
79
  head_rows.append(row)
80
  total += 1
81
+ info = f"分隔符: '{delim}' | 列: {len(headers)} | 预估(不含表头): ~{total}"
82
  preview = "\n".join([",".join(row) for row in head_rows])
83
  return info, ",".join(headers), preview
84
  except Exception as e:
 
94
  os.makedirs(save_dir, exist_ok=True)
95
  out_path = os.path.join(save_dir, 输出文件名)
96
 
 
97
  必要 = [时间列, 开列, 高列, 低列, 收列]
98
  if not all(必要):
99
+ return "请确保时间////收列已选择", ""
100
+
 
101
  delim = _sniff_delimiter(path)
102
  写入 = 0
103
  try:
 
105
  open(out_path, "w", encoding="utf-8", newline="") as w:
106
  r = csv.DictReader(f, delimiter=delim)
107
  wr = csv.writer(w)
108
+ wr.writerow(["时间","开","高","低","收","量"])
109
  for row in r:
110
  try:
111
  tms = _parse_time_to_ms(row[时间列])
112
+ o = float(row[开列]); h = float(row[高列]); l = float(row[低列]); c = float(row[收列])
 
 
 
113
  v = float(row[量列]) if 量列 and 量列 in row and row[量列] not in (None, "") else 0.0
114
  if tms is None:
115
  continue
 
121
  except Exception as e:
122
  return f"保存失败: {e}", ""
123
 
124
+ # ---------- 合约登记 ----------
125
+ 合同根 = os.path.join(默认保存根, "contracts")
126
+ os.makedirs(合同根, exist_ok=True)
127
+
128
+ def _key(exchange, inst): return f"{exchange}__{inst}"
129
+
130
+ def 登记合约(交易所, 合约ID, 别名, csv路径):
131
+ if not 交易所 or not 合约ID or not csv路径:
132
+ return "登记失败:缺少交易所/合约ID/CSV路径"
133
+ meta = {
134
+ "exchange": 交易所,
135
+ "contract_id": 合约ID,
136
+ "alias": 别名 or "",
137
+ "csv_path": csv路径,
138
+ "saved_at": datetime.utcnow().isoformat() + "Z"
139
+ }
140
+ path = os.path.join(合同根, _key(交易所, 合约ID) + ".json")
141
+ with open(path, "w", encoding="utf-8") as f:
142
+ json.dump(meta, f, ensure_ascii=False, indent=2)
143
+ return f"已登记合约:{交易所} | {合约ID}", path
144
+
145
+ def 列出已登记合约():
146
+ items = []
147
+ for fn in os.listdir(合同根):
148
+ if fn.endswith(".json"):
149
+ try:
150
+ with open(os.path.join(合同根, fn), "r", encoding="utf-8") as f:
151
+ m = json.load(f)
152
+ label = f"{m.get('exchange','?')} | {m.get('contract_id','?')}" + (f" ({m.get('alias')})" if m.get('alias') else "")
153
+ items.append((label, _key(m.get('exchange','?'), m.get('contract_id','?'))))
154
+ except Exception:
155
+ continue
156
+ # 返回 choices 列表(仅值)
157
+ return [v for _, v in sorted(items)]
158
+
159
+ def 读取登记(key):
160
+ path = os.path.join(合同根, key + ".json")
161
+ if not os.path.exists(path):
162
+ return None
163
+ with open(path, "r", encoding="utf-8") as f:
164
+ return json.load(f)
165
+
166
+ def 删除登记(key):
167
+ path = os.path.join(合同根, key + ".json")
168
+ if os.path.exists(path):
169
+ os.remove(path)
170
+ return True
171
+ return False
172
+
173
+ # ---------- 参数 ----------
174
  def 加载参数文件(param_file):
175
  if param_file is None:
176
  return json.dumps({"ver":"v1"}, ensure_ascii=False, indent=2)
 
205
  except Exception as e:
206
  return f"保存失败: {e}"
207
 
208
+ # ---------- 构建设置面板 ----------
209
  def 构建设置面板(容器, 默认参数=None, 记录函数=None):
 
 
 
 
210
  if 默认参数 is None:
211
  默认参数 = {"ver":"v1"}
212
 
 
215
  with gr.TabItem("参数配置"):
216
  with gr.Row():
217
  with gr.Column(scale=1):
218
+ gr.Markdown("### 策略参数")
219
+ 参数编辑框 = gr.Textbox(label="策略参数(JSON可编辑)", value=json.dumps(默认参数, ensure_ascii=False, indent=2), lines=18)
 
 
 
 
220
  with gr.Row():
221
  参数文件 = gr.File(label="上传参数JSON文件", file_types=[".json"])
222
  加载按钮 = gr.Button("加载到编辑框")
 
226
  参数保存名 = gr.Textbox(label="保存文件名", value="params.json")
227
  参数保存按钮 = gr.Button("保存参数到磁盘")
228
  保存结果 = gr.Textbox(label="保存结果", interactive=False)
229
+ 复制参数按钮 = gr.Button("将上方参数回填到运行页", variant="primary")
230
 
231
  with gr.Column(scale=1):
232
  gr.Markdown("小提示")
233
  gr.Markdown("- 可上传回测生成的最优参数JSON\n- 编辑后点“格式化/校验”再保存\n- 点击“将上方参数回填到运行页”即可带回主界面")
234
 
 
235
  加载按钮.click(加载参数文件, inputs=参数文件, outputs=参数编辑框)
236
  格式化按钮.click(格式化参数, inputs=参数编辑框, outputs=[参数编辑框, 加载结果])
237
  参数保存按钮.click(保存参数, inputs=[参数编辑框, 参数保存名], outputs=保存结果)
 
239
  with gr.TabItem("CSV上传"):
240
  with gr.Row():
241
  with gr.Column(scale=1):
242
+ gr.Markdown("#### 1) 上传与预览")
243
  csv文件 = gr.File(label="上传 1m K线 CSV", file_types=[".csv"])
 
 
244
  预览信息 = gr.Textbox(label="文件信息", lines=2, interactive=False)
245
  表头显示 = gr.Textbox(label="检测到的表头", lines=2, interactive=False)
246
  预览前几行 = gr.Textbox(label="前几行示例", lines=8, interactive=False)
247
+
248
+ gr.Markdown("#### 2) 列映射")
 
249
  时间列 = gr.Dropdown(label="时间列", choices=[], value=None)
250
  开列 = gr.Dropdown(label="开盘价列", choices=[], value=None)
251
  高列 = gr.Dropdown(label="最高价列", choices=[], value=None)
252
  低列 = gr.Dropdown(label="最低价列", choices=[], value=None)
253
  收列 = gr.Dropdown(label="收盘价列", choices=[], value=None)
254
  量列 = gr.Dropdown(label="成交量列(可选)", choices=[], value=None)
255
+
256
+ gr.Markdown("#### 3) 合约登记")
257
+ 交易所选择 = gr.Dropdown(label="交易所", choices=["OKX","BINANCE","BYBIT","DERIBIT","OTHER"], value="OKX")
258
+ 合约ID框 = gr.Textbox(label="合约ID(例:UXLINK-USDT-SWAP)", value="UXLINK-USDT-SWAP")
259
+ 合约别名 = gr.Textbox(label="合约别名(可选)", value="")
260
+ 输出文件名 = gr.Textbox(label="标准化输出文件名", value="uploaded_1m.csv")
261
+ 保存按钮 = gr.Button("标准化并登记", variant="primary")
262
  保存结果 = gr.Textbox(label="结果", interactive=False)
263
  输出路径 = gr.Textbox(label="输出路径", interactive=False)
264
+
265
+ gr.Markdown("#### 4) 已登记合约")
266
+ 已登记下拉 = gr.Dropdown(label="已登记合约", choices=列出已登记合约(), value=None)
267
+ with gr.Row():
268
+ 刷新登记按钮 = gr.Button("刷新列表")
269
+ 加载登记按钮 = gr.Button("加载登记到上方")
270
+ 删除登记按钮 = gr.Button("删除所选登记")
271
+ 回填合约按钮 = gr.Button("回填合约到运行页", variant="secondary")
272
+
273
  with gr.Column(scale=1):
274
+ gr.Markdown("使用步骤")
275
+ gr.Markdown("1. 上传 CSV → 预览及列映射\n2. 选择交易所/合约ID → 标准化并登记\n3. 可在右侧主界面启动模拟\n4. 如需再次使用,直接在“已登记合约”选择并加载")
 
276
 
277
+ # 上传后更新映射下拉
278
  def 处理CSV上传(file_obj):
 
 
 
279
  info, headers_str, preview = 读取CSV预览(file_obj)
280
  if not headers_str:
281
+ empty = gr.update(choices=[], value=None)
282
+ return info, headers_str, preview, empty, empty, empty, empty, empty, empty
283
  headers = headers_str.split(",")
284
  映射 = _guess_mapping(headers)
 
285
  return (
286
+ info, headers_str, preview,
287
+ gr.update(choices=headers, value=映射.get("时间")),
288
+ gr.update(choices=headers, value=映射.get("开")),
289
+ gr.update(choices=headers, value=映射.get("高")),
290
+ gr.update(choices=headers, value=映射.get("")),
291
+ gr.update(choices=headers, value=映射.get("")),
292
+ gr.update(choices=headers, value=映射.get("")),
 
 
 
293
  )
294
 
 
295
  csv文件.change(
296
  fn=处理CSV上传,
297
  inputs=csv文件,
298
+ outputs=[预览信息, 表头显示, 预览前几行, 时间列, 开列, 高列, 低列, 收列, 量列]
 
 
 
299
  )
300
+
301
+ # 保存并登记
302
+ def 保存并登记(file_obj, 输出名, 时间c, 开c, 高c, 低c, 收c, 量c, 交易所, 合约ID, 别名):
303
+ msg, path = 标准化CSV(file_obj, 输出名, 时间c, 开c, 高c, 低c, 收c, 量c)
304
+ if not path:
305
+ return msg, "", gr.update(choices=列出已登记合约()), ""
306
+ jmsg = ""
307
+ try:
308
+ jmsg, _ = 登记合约(交易所, 合约ID, 别名, path)
309
+ except Exception as e:
310
+ jmsg = f"登记失败:{e}"
311
+ # 更新列表并选中新登记
312
+ key = _key(交易所, 合约ID)
313
+ return f"{msg}\n{jmsg}", path, gr.update(choices=列出已登记合约(), value=key), key
314
+
315
+ 新登记键 = gr.Textbox(visible=False)
316
  保存按钮.click(
317
+ 保存并登记,
318
+ inputs=[csv文件, 输出文件名, 时间列, 开列, 高列, 低列, 收列, 量列, 交易所选择, 合约ID框, 合约别名],
319
+ outputs=[保存结果, 输出路径, 已登记下拉, 新登记键]
320
  )
321
 
322
+ # 刷新/加载/删除
323
+ 刷新登记按钮.click(lambda: gr.update(choices=列出已登记合约(), value=None), outputs=已登记下拉)
324
+
325
+ def do_load(key):
326
+ m = 读取登记(key) if key else None
327
+ if not m:
328
+ return (gr.update(), gr.update(), gr.update(), "")
329
+ return (gr.update(value=m.get("exchange","OKX")),
330
+ gr.update(value=m.get("contract_id","")),
331
+ gr.update(value=m.get("alias","")),
332
+ m.get("csv_path",""))
333
+ 加载登记按钮.click(do_load, inputs=已登记下拉, outputs=[交易所选择, 合约ID框, 合约别名, 输出路径])
334
+
335
+ def do_del(key):
336
+ if not key:
337
+ return "请先选择一条登记", gr.update(choices=列出已登记合约(), value=None)
338
+ ok = 删除登记(key)
339
+ msg = "已删除" if ok else "删除失败(不存在或权限)"
340
+ return msg, gr.update(choices=列出已登记合约(), value=None)
341
+ 删除登记按钮.click(do_del, inputs=已登记下拉, outputs=[保存结果, 已登记下拉])
342
+
343
  with gr.TabItem("系统"):
344
  修复按钮 = gr.Button("修复导入路径/别名(重试)")
345
  修复结果 = gr.Textbox(label="结果", interactive=False)
 
363
  return f"失败:{e}"
364
  修复按钮.click(do_fix, outputs=修复结果)
365
 
366
+ # 返回用于与主界面联动的组件
367
+ return {
368
+ "参数编辑框": 参数编辑框,
369
+ "复制参数按钮": 复制参数按钮,
370
+ "合约ID框": 合约ID框,
371
+ "回填合约按钮": 回填合约按钮
372
+ }