#!/usr/bin/env python3 """ prepare_recommendation.py - 标准化操作建议前置流程 调用方式: python3 prepare_recommendation.py 输出: JSON,包含: - strategy: 信号/止损/止盈/动作 - trade_constraints: 市场/最小交易单位/可否拆半手 - pnl: 成本/盈亏金额/盈亏比例 (仅持仓股) - timing_signal: 系统策略信号 - action_note: 策略建议说明 依赖: per_stock_reassess.py, holdings/portfolio数据 """ import sys import json import os import sqlite3 DB_PATH = "/home/hmo/MoFin/data/mofin.db" SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) def get_stock_info(code): """从数据库获取股票信息""" db = sqlite3.connect(DB_PATH) db.row_factory = sqlite3.Row try: # 检查持仓 row = db.execute( "SELECT * FROM holdings WHERE code=? AND is_active=1 AND shares>0", (code,) ).fetchone() if row: hold = dict(row) hold['is_holding'] = True else: hold = {'is_holding': False} # 检查是否是港股 hk_codes = ['0' + str(i) for i in range(1, 100)] # 港股5位码 is_hk = code.isdigit() and not code.startswith(('6', '3', '0')) if code.startswith(('01', '02', '09')): is_hk = True hold['market'] = 'HK' if is_hk else 'CN' # 最小交易单位 if is_hk: hold['min_lot'] = 100 hold['can_split'] = False elif code.startswith('688'): hold['min_lot'] = 200 hold['can_split'] = False else: hold['min_lot'] = 100 hold['can_split'] = True # A股可以拆散 return hold finally: db.close() def run_reassess(code, price): """调用per_stock_reassess获取策略信号""" reassess_script = os.path.join(SCRIPTS_DIR, "per_stock_reassess.py") import subprocess result = subprocess.run( ["python3", reassess_script, code, str(price)], capture_output=True, text=True, timeout=60 ) return result.stdout def parse_reassess_output(output): """从per_stock_reassess输出中提取关键信号""" result = { 'timing_signal': 'unknown', 'action_note': '', 'stop_loss': None, 'take_profit': None, 'buy_zone_low': None, 'buy_zone_high': None, 'rr': None, } for line in output.split('\n'): line = line.strip() if '[OK]' in line: # 格式: [OK] 688981 中芯国际: 盈利持有 | ⚠️盈亏比不足1:1.5 | ... | 信号:买入 parts = line.split(': ', 2) if len(parts) >= 2: action_part = parts[-1] result['action_note'] = action_part # 提取信号 if '信号:' in action_part: signal = action_part.split('信号:')[-1].split()[0] result['timing_signal'] = signal # 提取止损 if '止损' in action_part: import re m = re.search(r'止损(\d+\.?\d*)', action_part) if m: result['stop_loss'] = float(m.group(1)) # 提取目标/止盈 if '目标' in action_part: import re m = re.search(r'目标(\d+\.?\d*)', action_part) if m: result['take_profit'] = float(m.group(1)) # 提取买入区 if '买入区' in action_part: import re m = re.search(r'买入区(\d+\.?\d*)~(\d+\.?\d*)', action_part) if m: result['buy_zone_low'] = float(m.group(1)) result['buy_zone_high'] = float(m.group(2)) # 提取RR if 'RR' in action_part: import re m = re.search(r'RR(\d+\.?\d*)', action_part) if m: result['rr'] = float(m.group(1)) if 'factors=' in line: import re m = re.search(r"factors=\['(.*?)'\]", line) if m: factors = m.group(1).split("', '") result['factors'] = factors return result def build_output(code, price, stock_info, strategy): """构建标准化JSON输出""" output = { 'code': code, 'price': price, 'market': stock_info.get('market', 'CN'), 'strategy': { 'timing_signal': strategy.get('timing_signal', 'unknown'), 'action_note': strategy.get('action_note', ''), 'stop_loss': strategy.get('stop_loss'), 'take_profit': strategy.get('take_profit'), 'buy_zone': { 'low': strategy.get('buy_zone_low'), 'high': strategy.get('buy_zone_high') }, 'rr': strategy.get('rr'), 'factors': strategy.get('factors', []), }, 'trade_constraints': { 'market': '港股' if stock_info.get('market') == 'HK' else 'A股', 'min_trading_unit': stock_info.get('min_lot', 100), 'can_split_lot': stock_info.get('can_split', True), 'note': ( f"港股每手{stock_info.get('min_lot', 100)}股不能拆半手" if stock_info.get('market') == 'HK' else f"科创板每手{stock_info.get('min_lot', 200)}股不能拆半手" if stock_info.get('min_lot') == 200 else "A股可拆散交易" ) }, 'pnl': None, } if stock_info.get('is_holding'): cost = stock_info.get('cost', 0) shares = stock_info.get('shares', 0) market_value = price * shares cost_value = cost * shares pnl_amount = market_value - cost_value pnl_pct = ((price / cost) - 1) * 100 if cost else 0 output['pnl'] = { 'cost_price': cost, 'shares': shares, 'market_value': round(market_value, 2), 'cost_value': round(cost_value, 2), 'pnl_amount': round(pnl_amount, 2), 'pnl_pct': round(pnl_pct, 2), 'status': '浮盈' if pnl_amount >= 0 else '浮亏', } return output def main(): if len(sys.argv) < 3: print(json.dumps({ 'error': '用法: python3 prepare_recommendation.py ' }, ensure_ascii=False)) sys.exit(1) code = sys.argv[1] try: price = float(sys.argv[2]) except ValueError: print(json.dumps({'error': f'无效价格: {sys.argv[2]}'}, ensure_ascii=False)) sys.exit(1) # 获取股票信息(市场/最小单位/持仓) stock_info = get_stock_info(code) # 运行策略重评 reassess_output = run_reassess(code, price) strategy = parse_reassess_output(reassess_output) # 构建输出 output = build_output(code, price, stock_info, strategy) # 输出JSON print(json.dumps(output, ensure_ascii=False, indent=2)) if __name__ == '__main__': main()