monitor.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. """
  2. 监控记录相关路由
  3. """
  4. from flask import Blueprint, jsonify, request, render_template, send_file, make_response
  5. from app import db
  6. from app.models.monitor import MonitorRecord
  7. from app.models.future_info import FutureInfo
  8. from app.models.dimension import PositionMode, CandleInfo
  9. import pandas as pd
  10. import io
  11. import os
  12. from datetime import datetime
  13. from werkzeug.utils import secure_filename
  14. from io import BytesIO
  15. from openpyxl.utils import get_column_letter
  16. bp = Blueprint('monitor', __name__, url_prefix='/monitor')
  17. @bp.route('/', methods=['GET'])
  18. def index():
  19. """监控记录列表页面"""
  20. return render_template('monitor/index.html')
  21. @bp.route('/add', methods=['GET'])
  22. def add():
  23. """添加监控记录页面"""
  24. return render_template('monitor/add.html')
  25. @bp.route('/list', methods=['GET'])
  26. def get_list():
  27. """获取监控记录列表"""
  28. # 获取筛选参数
  29. status_list = request.args.getlist('status')
  30. market = request.args.get('market')
  31. names = request.args.getlist('name')
  32. contract_letters = request.args.getlist('contract_letter')
  33. # 构建查询
  34. query = MonitorRecord.query
  35. if status_list:
  36. # 支持多个状态值筛选
  37. status_ints = [int(s) for s in status_list if s.isdigit()]
  38. if status_ints:
  39. query = query.filter(MonitorRecord.status.in_(status_ints))
  40. if market is not None:
  41. query = query.filter(MonitorRecord.market == int(market))
  42. if names:
  43. query = query.filter(MonitorRecord.name.in_(names))
  44. if contract_letters:
  45. # 假设合约代码的前1-2位是合约字母
  46. query = query.filter(db.or_(*[MonitorRecord.contract.startswith(letter) for letter in contract_letters]))
  47. # 执行查询
  48. monitors = query.all()
  49. # 返回结果
  50. return jsonify({
  51. 'code': 0,
  52. 'msg': '成功',
  53. 'data': [monitor.to_dict() for monitor in monitors]
  54. })
  55. @bp.route('/detail/<int:id>', methods=['GET'])
  56. def detail(id):
  57. """监控记录详情页面"""
  58. monitor = MonitorRecord.query.get_or_404(id)
  59. return render_template('monitor/detail.html', monitor=monitor)
  60. @bp.route('/edit/<int:id>', methods=['GET'])
  61. def edit(id):
  62. """编辑监控记录页面"""
  63. monitor = MonitorRecord.query.get_or_404(id)
  64. return render_template('monitor/edit.html', monitor=monitor)
  65. @bp.route('/api/detail/<int:id>', methods=['GET'])
  66. def get_detail(id):
  67. """获取监控记录详情API"""
  68. monitor = MonitorRecord.query.get_or_404(id)
  69. return jsonify({
  70. 'code': 0,
  71. 'msg': '成功',
  72. 'data': monitor.to_dict()
  73. })
  74. @bp.route('/create', methods=['POST'])
  75. def create():
  76. """创建监控记录"""
  77. data = request.json
  78. # 获取关键价格
  79. key_price = data.get('key_price')
  80. future_info_id = data.get('future_info_id')
  81. # 初始化价格字段
  82. open_long_price = data.get('open_long_price')
  83. open_short_price = data.get('open_short_price')
  84. open_long_trigger_price = data.get('open_long_trigger_price')
  85. open_short_trigger_price = data.get('open_short_trigger_price')
  86. # 从配置服务获取默认监控状态和价格计算系数
  87. try:
  88. from app.services.config_service import get_int_config, get_float_config
  89. default_monitor_status = get_int_config('default_monitor_status', 0)
  90. long_price_ratio = get_float_config('long_position_price_ratio', 2/3)
  91. short_price_ratio = get_float_config('short_position_price_ratio', 2/3)
  92. except Exception as e:
  93. print(f"[WARNING] 获取配置失败,使用硬编码值: {e}")
  94. default_monitor_status = 0
  95. long_price_ratio = 2/3
  96. short_price_ratio = 2/3
  97. # 如果有关键价格和期货信息ID,则进行自动计算
  98. if key_price is not None and future_info_id:
  99. future_info = FutureInfo.query.get(future_info_id)
  100. if future_info and future_info.core_ratio is not None:
  101. core_ratio = future_info.core_ratio
  102. # 自动计算各个价格
  103. # 做多开仓价 = 关键价格 * (1 + 核心比率 * 2/3)
  104. if open_long_price is None:
  105. open_long_price = key_price * (1 + core_ratio * 2/3)
  106. open_long_price = round(open_long_price, 2)
  107. # 做空开仓价 = 关键价格 * (1 - 核心比率 * 2/3)
  108. if open_short_price is None:
  109. open_short_price = key_price * (1 - core_ratio * 2/3)
  110. open_short_price = round(open_short_price, 2)
  111. # 做多触发价 = 关键价格 * (1 + 核心比率)
  112. if open_long_trigger_price is None:
  113. open_long_trigger_price = key_price * (1 + core_ratio)
  114. open_long_trigger_price = round(open_long_trigger_price, 2)
  115. # 做空触发价 = 关键价格 * (1 - 核心比率)
  116. if open_short_trigger_price is None:
  117. open_short_trigger_price = key_price * (1 - core_ratio)
  118. open_short_trigger_price = round(open_short_trigger_price, 2)
  119. # 创建新记录
  120. monitor = MonitorRecord(
  121. contract=data.get('contract'),
  122. name=data.get('name'),
  123. market=data.get('market'),
  124. opportunity=data.get('opportunity'),
  125. key_price=key_price,
  126. open_long_price=open_long_price,
  127. open_short_price=open_short_price,
  128. status=data.get('status', default_monitor_status),
  129. latest_price=data.get('latest_price'),
  130. open_long_trigger_price=open_long_trigger_price,
  131. open_short_trigger_price=open_short_trigger_price,
  132. open_long_margin_per_unit=data.get('open_long_margin_per_unit'),
  133. open_short_margin_per_unit=data.get('open_short_margin_per_unit'),
  134. candle_pattern_id=data.get('candle_pattern_id'),
  135. candle_pattern=data.get('candle_pattern'),
  136. candle_pattern_ids=data.get('candle_pattern_ids'),
  137. long_trend_ids=data.get('long_trend_ids'),
  138. long_trend_name=data.get('long_trend_name'),
  139. mid_trend_ids=data.get('mid_trend_ids'),
  140. mid_trend_name=data.get('mid_trend_name'),
  141. similarity_evaluation=data.get('similarity_evaluation'),
  142. possible_trigger_price=data.get('possible_trigger_price'),
  143. reference_price_type=data.get('reference_price_type'),
  144. relative_ratio=data.get('relative_ratio'),
  145. contract_letter=data.get('contract_letter'),
  146. open_price=data.get('open_price'),
  147. position_mode_id=data.get('position_mode_id')
  148. )
  149. # 保存到数据库
  150. try:
  151. print(f"[DEBUG] 准备保存监控记录: contract={monitor.contract}, name={monitor.name}")
  152. db.session.add(monitor)
  153. db.session.commit()
  154. print(f"[DEBUG] 监控记录保存成功,ID: {monitor.id}")
  155. return jsonify({
  156. 'code': 0,
  157. 'msg': '创建成功',
  158. 'data': monitor.to_dict()
  159. })
  160. except Exception as e:
  161. print(f"[ERROR] 保存监控记录失败: {e}")
  162. db.session.rollback()
  163. return jsonify({
  164. 'code': 1,
  165. 'msg': f'保存失败: {str(e)}'
  166. }), 500
  167. @bp.route('/add', methods=['POST'])
  168. def add_post():
  169. """创建监控记录(通过add路由)"""
  170. data = request.json
  171. print(f"[DEBUG] 监控添加请求数据: {data}")
  172. # 从配置服务获取默认监控状态
  173. try:
  174. from app.services.config_service import get_int_config
  175. default_monitor_status = get_int_config('default_monitor_status', 0)
  176. except Exception as e:
  177. print(f"[WARNING] 获取默认监控状态配置失败,使用硬编码值: {e}")
  178. default_monitor_status = 0
  179. # 获取关键价格
  180. key_price = data.get('key_price')
  181. future_info_id = data.get('future_info_id')
  182. # 初始化价格字段
  183. open_long_price = data.get('open_long_price')
  184. open_short_price = data.get('open_short_price')
  185. open_long_trigger_price = data.get('open_long_trigger_price')
  186. open_short_trigger_price = data.get('open_short_trigger_price')
  187. # 如果有关键价格和期货信息ID,则进行自动计算
  188. if key_price is not None and future_info_id:
  189. future_info = FutureInfo.query.get(future_info_id)
  190. if future_info and future_info.core_ratio is not None:
  191. core_ratio = future_info.core_ratio
  192. # 自动计算各个价格
  193. # 做多开仓价 = 关键价格 * (1 + 核心比率 * 2/3)
  194. if open_long_price is None:
  195. open_long_price = key_price * (1 + core_ratio * 2/3)
  196. open_long_price = round(open_long_price, 2)
  197. # 做空开仓价 = 关键价格 * (1 - 核心比率 * 2/3)
  198. if open_short_price is None:
  199. open_short_price = key_price * (1 - core_ratio * 2/3)
  200. open_short_price = round(open_short_price, 2)
  201. # 做多触发价 = 关键价格 * (1 + 核心比率)
  202. if open_long_trigger_price is None:
  203. open_long_trigger_price = key_price * (1 + core_ratio)
  204. open_long_trigger_price = round(open_long_trigger_price, 2)
  205. # 做空触发价 = 关键价格 * (1 - 核心比率)
  206. if open_short_trigger_price is None:
  207. open_short_trigger_price = key_price * (1 - core_ratio)
  208. open_short_trigger_price = round(open_short_trigger_price, 2)
  209. # 创建新记录
  210. monitor = MonitorRecord(
  211. contract=data.get('contract'),
  212. name=data.get('name'),
  213. market=data.get('market'),
  214. opportunity=data.get('opportunity'),
  215. key_price=key_price,
  216. open_long_price=open_long_price,
  217. open_short_price=open_short_price,
  218. status=data.get('status', default_monitor_status),
  219. latest_price=data.get('latest_price'),
  220. open_long_trigger_price=open_long_trigger_price,
  221. open_short_trigger_price=open_short_trigger_price,
  222. open_long_margin_per_unit=data.get('open_long_margin_per_unit'),
  223. open_short_margin_per_unit=data.get('open_short_margin_per_unit'),
  224. candle_pattern_id=data.get('candle_pattern_id'),
  225. candle_pattern=data.get('candle_pattern'),
  226. candle_pattern_ids=data.get('candle_pattern_ids'),
  227. long_trend_ids=data.get('long_trend_ids'),
  228. long_trend_name=data.get('long_trend_name'),
  229. mid_trend_ids=data.get('mid_trend_ids'),
  230. mid_trend_name=data.get('mid_trend_name'),
  231. similarity_evaluation=data.get('similarity_evaluation'),
  232. possible_trigger_price=data.get('possible_trigger_price'),
  233. reference_price_type=data.get('reference_price_type'),
  234. relative_ratio=data.get('relative_ratio'),
  235. contract_letter=data.get('contract_letter'),
  236. open_price=data.get('open_price'),
  237. position_mode_id=data.get('position_mode_id')
  238. )
  239. # 保存到数据库
  240. try:
  241. print(f"[DEBUG] 准备保存监控记录: contract={monitor.contract}, name={monitor.name}")
  242. db.session.add(monitor)
  243. db.session.commit()
  244. print(f"[DEBUG] 监控记录保存成功,ID: {monitor.id}")
  245. return jsonify({
  246. 'code': 0,
  247. 'msg': '创建成功',
  248. 'data': monitor.to_dict()
  249. })
  250. except Exception as e:
  251. print(f"[ERROR] 保存监控记录失败: {e}")
  252. db.session.rollback()
  253. return jsonify({
  254. 'code': 1,
  255. 'msg': f'保存失败: {str(e)}'
  256. }), 500
  257. @bp.route('/update/<int:id>', methods=['PUT'])
  258. def update(id):
  259. """更新监控记录"""
  260. monitor = MonitorRecord.query.get_or_404(id)
  261. data = request.json
  262. # 更新字段
  263. if 'contract' in data:
  264. monitor.contract = data['contract']
  265. if 'name' in data:
  266. monitor.name = data['name']
  267. if 'market' in data:
  268. monitor.market = data['market']
  269. if 'opportunity' in data:
  270. monitor.opportunity = data['opportunity']
  271. if 'key_price' in data:
  272. monitor.key_price = data['key_price']
  273. if 'open_long_price' in data:
  274. monitor.open_long_price = data['open_long_price']
  275. if 'open_short_price' in data:
  276. monitor.open_short_price = data['open_short_price']
  277. if 'status' in data:
  278. monitor.status = data['status']
  279. if 'latest_price' in data:
  280. monitor.latest_price = data['latest_price']
  281. if 'open_long_trigger_price' in data:
  282. monitor.open_long_trigger_price = data['open_long_trigger_price']
  283. if 'open_short_trigger_price' in data:
  284. monitor.open_short_trigger_price = data['open_short_trigger_price']
  285. if 'open_long_margin_per_unit' in data:
  286. monitor.open_long_margin_per_unit = data['open_long_margin_per_unit']
  287. if 'open_short_margin_per_unit' in data:
  288. monitor.open_short_margin_per_unit = data['open_short_margin_per_unit']
  289. if 'candle_pattern_id' in data:
  290. monitor.candle_pattern_id = data['candle_pattern_id']
  291. if 'candle_pattern' in data:
  292. monitor.candle_pattern = data['candle_pattern']
  293. if 'candle_pattern_ids' in data:
  294. monitor.candle_pattern_ids = data['candle_pattern_ids']
  295. if 'long_trend_ids' in data:
  296. monitor.long_trend_ids = data['long_trend_ids']
  297. if 'long_trend_name' in data:
  298. monitor.long_trend_name = data['long_trend_name']
  299. if 'mid_trend_ids' in data:
  300. monitor.mid_trend_ids = data['mid_trend_ids']
  301. if 'mid_trend_name' in data:
  302. monitor.mid_trend_name = data['mid_trend_name']
  303. if 'similarity_evaluation' in data:
  304. monitor.similarity_evaluation = data['similarity_evaluation']
  305. if 'possible_trigger_price' in data:
  306. monitor.possible_trigger_price = data['possible_trigger_price']
  307. if 'reference_price_type' in data:
  308. monitor.reference_price_type = data['reference_price_type']
  309. if 'relative_ratio' in data:
  310. monitor.relative_ratio = data['relative_ratio']
  311. if 'contract_letter' in data:
  312. monitor.contract_letter = data['contract_letter']
  313. if 'open_price' in data:
  314. monitor.open_price = data['open_price']
  315. if 'position_mode_id' in data:
  316. monitor.position_mode_id = data['position_mode_id']
  317. # 保存到数据库
  318. try:
  319. db.session.commit()
  320. print(f"[DEBUG] 监控记录更新成功,ID: {monitor.id}")
  321. except Exception as e:
  322. print(f"[ERROR] 更新监控记录失败: {e}")
  323. db.session.rollback()
  324. return jsonify({
  325. 'code': 1,
  326. 'msg': f'更新失败: {str(e)}'
  327. }), 500
  328. return jsonify({
  329. 'code': 0,
  330. 'msg': '更新成功',
  331. 'data': monitor.to_dict()
  332. })
  333. @bp.route('/invalidate/<int:id>', methods=['PUT'])
  334. def invalidate(id):
  335. """标记监控记录为失效"""
  336. monitor = MonitorRecord.query.get_or_404(id)
  337. # 更新状态为已失效(3)
  338. monitor.status = 3
  339. try:
  340. db.session.commit()
  341. return jsonify({
  342. 'code': 0,
  343. 'msg': '标记失效成功',
  344. 'data': monitor.to_dict()
  345. })
  346. except Exception as e:
  347. db.session.rollback()
  348. return jsonify({
  349. 'code': 1,
  350. 'msg': f'标记失效失败: {str(e)}'
  351. }), 500
  352. @bp.route('/delete/<int:id>', methods=['DELETE'])
  353. def delete(id):
  354. """删除监控记录"""
  355. monitor = MonitorRecord.query.get_or_404(id)
  356. # 从数据库删除
  357. db.session.delete(monitor)
  358. db.session.commit()
  359. return jsonify({
  360. 'code': 0,
  361. 'msg': '删除成功'
  362. })
  363. @bp.route('/import', methods=['GET'])
  364. def import_view():
  365. """导入监控记录页面"""
  366. return render_template('monitor/import.html')
  367. @bp.route('/get_template', methods=['GET'])
  368. def get_template():
  369. """Generate and return an Excel template for data import."""
  370. # 创建一个DataFrame,包含需要的列
  371. df = pd.DataFrame(columns=[
  372. '合约代码', '名称', '市场类型', '关注原因', '关注状态', '备注'
  373. ])
  374. # 添加示例数据(可选)
  375. df.loc[0] = ['IF2212', '沪深300期货2212', '0', '价格突破', '1', '重点关注']
  376. df.loc[1] = ['SC2301', '原油期货2301', '1', '季节性变化', '0', '暂时观察']
  377. # 创建一个字节流
  378. output = BytesIO()
  379. # 使用ExcelWriter以便于设置列宽
  380. with pd.ExcelWriter(output, engine='openpyxl') as writer:
  381. df.to_excel(writer, index=False, sheet_name='监控记录导入模板')
  382. worksheet = writer.sheets['监控记录导入模板']
  383. # 调整列宽
  384. for i, col in enumerate(df.columns):
  385. column_width = max(len(col) * 2, 15)
  386. worksheet.column_dimensions[get_column_letter(i + 1)].width = column_width
  387. output.seek(0)
  388. # 返回Excel文件
  389. return send_file(
  390. output,
  391. as_attachment=True,
  392. download_name='监控记录导入模板.xlsx',
  393. mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  394. )
  395. @bp.route('/import', methods=['POST'])
  396. def import_excel():
  397. """Import monitor records from Excel file."""
  398. if 'file' not in request.files:
  399. return jsonify({
  400. 'code': 1,
  401. 'msg': '没有上传文件'
  402. })
  403. file = request.files['file']
  404. if file.filename == '':
  405. return jsonify({
  406. 'code': 1,
  407. 'msg': '未选择文件'
  408. })
  409. if not file.filename.endswith('.xlsx'):
  410. return jsonify({
  411. 'code': 1,
  412. 'msg': '请上传Excel文件(.xlsx)'
  413. })
  414. try:
  415. # 读取Excel文件
  416. df = pd.read_excel(file)
  417. # 检查必需的列
  418. required_columns = ['合约代码', '名称', '市场类型']
  419. missing_columns = [col for col in required_columns if col not in df.columns]
  420. if missing_columns:
  421. return jsonify({
  422. 'code': 1,
  423. 'msg': f'缺少必要的列: {", ".join(missing_columns)}'
  424. })
  425. # 准备导入数据
  426. success_count = 0
  427. error_count = 0
  428. error_messages = []
  429. for index, row in df.iterrows():
  430. try:
  431. # 检查必填字段
  432. if pd.isna(row['合约代码']) or pd.isna(row['名称']) or pd.isna(row['市场类型']):
  433. error_count += 1
  434. error_messages.append(f"第{index+2}行: 合约代码、名称和市场类型为必填项")
  435. continue
  436. # 创建监控记录
  437. monitor = MonitorRecord(
  438. contract=row['合约代码'],
  439. name=row['名称'],
  440. market=int(row['市场类型']),
  441. opportunity=row['关注原因'] if not pd.isna(row['关注原因']) else None,
  442. status=int(row['关注状态']) if not pd.isna(row['关注状态']) else 0
  443. )
  444. db.session.add(monitor)
  445. success_count += 1
  446. except Exception as e:
  447. error_count += 1
  448. error_messages.append(f"第{index+2}行: {str(e)}")
  449. # 提交事务
  450. db.session.commit()
  451. return jsonify({
  452. 'code': 0,
  453. 'msg': f'成功导入{success_count}条记录',
  454. 'data': {
  455. 'success_count': success_count,
  456. 'error_count': error_count,
  457. 'error_messages': error_messages
  458. }
  459. })
  460. except Exception as e:
  461. db.session.rollback()
  462. return jsonify({
  463. 'code': 1,
  464. 'msg': f'导入失败: {str(e)}'
  465. })
  466. # 新增 API 端点:根据 future_info_id 或 contract_code 查询 FutureInfo
  467. @bp.route('/api/future_info/lookup', methods=['GET'])
  468. def lookup_future_info():
  469. future_info_id = request.args.get('future_info_id', type=int)
  470. contract_code = request.args.get('contract_code')
  471. future = None
  472. if future_info_id:
  473. future = FutureInfo.query.get(future_info_id)
  474. elif contract_code:
  475. # 从合约代码中提取合约字母:去掉右侧4位数字
  476. import re
  477. # 使用正则表达式匹配:字母开头,后面跟4位数字
  478. match = re.match(r'^([A-Za-z]+)(\d{4})$', contract_code)
  479. if match:
  480. letter = match.group(1) # 提取字母部分
  481. # 精确匹配合约字母
  482. future = FutureInfo.query.filter(
  483. FutureInfo.contract_letter == letter
  484. ).first()
  485. if future:
  486. return jsonify({
  487. 'code': 0,
  488. 'msg': '成功',
  489. 'data': {
  490. 'name': future.name,
  491. 'market_type': future.market # 修正字段名称
  492. }
  493. })
  494. else:
  495. return jsonify({'code': 1, 'msg': '未找到匹配的期货信息', 'data': None})
  496. # 新增 API 端点:根据期货信息ID和关键价格计算各个价格
  497. @bp.route('/api/calculate_prices', methods=['POST'])
  498. def calculate_prices():
  499. """根据期货信息ID和关键价格计算各个价格"""
  500. data = request.json
  501. future_info_id = data.get('future_info_id')
  502. key_price = data.get('key_price')
  503. if not future_info_id or key_price is None:
  504. return jsonify({
  505. 'code': 1,
  506. 'msg': '缺少必要参数:future_info_id 或 key_price'
  507. })
  508. # 查询期货信息
  509. future_info = FutureInfo.query.get(future_info_id)
  510. if not future_info:
  511. return jsonify({
  512. 'code': 1,
  513. 'msg': '未找到对应的期货信息'
  514. })
  515. if future_info.core_ratio is None:
  516. return jsonify({
  517. 'code': 1,
  518. 'msg': '该期货品种未设置核心比率'
  519. })
  520. core_ratio = future_info.core_ratio
  521. # 计算各个价格,保留2位小数
  522. calculated_prices = {
  523. 'open_long_price': round(key_price * (1 + core_ratio * 2/3), 2), # 做多开仓价
  524. 'open_short_price': round(key_price * (1 - core_ratio * 2/3), 2), # 做空开仓价
  525. 'open_long_trigger_price': round(key_price * (1 + core_ratio), 2), # 做多触发价
  526. 'open_short_trigger_price': round(key_price * (1 - core_ratio), 2), # 做空触发价
  527. 'core_ratio': core_ratio # 返回核心比率用于显示
  528. }
  529. return jsonify({
  530. 'code': 0,
  531. 'msg': '计算成功',
  532. 'data': calculated_prices
  533. })
  534. # 新增 API 端点:获取开仓模式列表
  535. @bp.route('/api/position_modes', methods=['GET'])
  536. def get_position_modes():
  537. """获取开仓模式列表"""
  538. modes = PositionMode.query.all()
  539. return jsonify({
  540. 'code': 0,
  541. 'msg': '成功',
  542. 'data': [mode.to_dict() for mode in modes]
  543. })
  544. # 新增 API 端点:获取K线形态列表
  545. @bp.route('/api/candle_patterns', methods=['GET'])
  546. def get_candle_patterns():
  547. """获取K线形态列表"""
  548. patterns = CandleInfo.query.all()
  549. return jsonify({
  550. 'code': 0,
  551. 'msg': '成功',
  552. 'data': [pattern.to_dict() for pattern in patterns]
  553. })
  554. # 新增 API 端点:根据合约字母获取保证金信息
  555. @bp.route('/api/margin_info/<contract_letter>', methods=['GET'])
  556. def get_margin_info(contract_letter):
  557. """根据合约字母获取保证金信息"""
  558. future_info = FutureInfo.query.filter_by(contract_letter=contract_letter).first()
  559. if future_info:
  560. return jsonify({
  561. 'code': 0,
  562. 'msg': '成功',
  563. 'data': {
  564. 'long_margin_amount': future_info.long_margin_amount,
  565. 'short_margin_amount': future_info.short_margin_amount,
  566. 'contract_multiplier': future_info.contract_multiplier
  567. }
  568. })
  569. else:
  570. return jsonify({
  571. 'code': 1,
  572. 'msg': '未找到对应的期货信息',
  573. 'data': None
  574. })