Files
MoFin/deploy/profile-scripts/prepare_recommendation.py
知微 80d59c9331 feat: 统一部署目录——所有运行时文件归入MoFin repo
- deploy/bot/ — XMPP bot核心(xmpp_agent_core + xmpp_zhiwei_bot)
- deploy/profile-scripts/ — cron脚本(price_monitor等)
- 运行时文件已替换为指向MoFin的符号链接
- 改代码只需改MoFin,系统自动生效
2026-07-17 23:12:35 +08:00

212 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""
prepare_recommendation.py - 标准化操作建议前置流程
调用方式: python3 prepare_recommendation.py <code> <current_price>
输出: 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 <code> <price>'
}, 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()