feat: 统一部署目录——所有运行时文件归入MoFin repo
- deploy/bot/ — XMPP bot核心(xmpp_agent_core + xmpp_zhiwei_bot) - deploy/profile-scripts/ — cron脚本(price_monitor等) - 运行时文件已替换为指向MoFin的符号链接 - 改代码只需改MoFin,系统自动生效
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user