|
|
|
|
|
|
|
|
import sys |
|
|
import os |
|
|
from datetime import datetime |
|
|
from pathlib import Path |
|
|
from dotenv import load_dotenv |
|
|
import time |
|
|
|
|
|
|
|
|
import matplotlib |
|
|
matplotlib.use('Agg') |
|
|
|
|
|
sys.path.append(os.path.abspath(os.path.dirname(__file__))) |
|
|
from catl_data_functions import fetch_stock_data |
|
|
from stock_chart_tools import generate_stock_charts |
|
|
|
|
|
from evoagentx.models import OpenAILLMConfig, OpenAILLM, OpenRouterConfig, OpenRouterLLM |
|
|
from evoagentx.workflow import WorkFlowGraph, WorkFlow, WorkFlowGenerator |
|
|
from evoagentx.agents import AgentManager |
|
|
from evoagentx.tools import StorageToolkit, CMDToolkit |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
def read_api_key(filename): |
|
|
try: |
|
|
with open(filename, 'r', encoding='utf-8') as f: |
|
|
return f.read().strip() |
|
|
except FileNotFoundError: |
|
|
return None |
|
|
|
|
|
OPENAI_API_KEY = read_api_key("openai_api_key.txt") or os.getenv("OPENAI_API_KEY") |
|
|
OPEN_ROUTER_API_KEY = read_api_key("openrouter_api_key.txt") or os.getenv("OPENROUTER_API_KEY") |
|
|
|
|
|
|
|
|
available_funds = 100000 |
|
|
current_positions = 500 |
|
|
average_price = 280 |
|
|
position_type = "call" |
|
|
report_date = datetime.now().strftime('%Y-%m-%d') |
|
|
llm = OpenAILLM(config=OpenAILLMConfig(model="gpt-4o-mini", openai_key=OPENAI_API_KEY, stream=True, output_response=True, max_tokens=16000)) |
|
|
tools = [StorageToolkit(), CMDToolkit()] |
|
|
|
|
|
|
|
|
module_save_path = "invest_demo_4o_mini_v1.json" |
|
|
|
|
|
|
|
|
WORKFLOW_GOAL = """Create a daily trading decision workflow for A-share stocks. |
|
|
|
|
|
## Workflow Overview: |
|
|
A multi-step workflow for daily trading decisions with fixed capital, making trading decisions based on market data and current positions. |
|
|
|
|
|
## Task Description: |
|
|
**Name:** daily_trading_decision |
|
|
**Description:** A comprehensive trading decision system that analyzes market data and generates daily trading operations with detailed analysis. |
|
|
|
|
|
## Input: |
|
|
- **goal** (string): Contains stock code, available funds, current positions, data folder path, output file path, and optional past report path |
|
|
|
|
|
## Output: |
|
|
- **trading_report** (string): A comprehensive daily trading report with complete analysis |
|
|
|
|
|
## Analysis Requirements: |
|
|
The workflow should analyze three key aspects of the stock: |
|
|
|
|
|
1. **Background Analysis**: Market environment, industry trends, news sentiment, expert opinions, economic factors, and regulatory environment that affect stock prices |
|
|
2. **Price Analysis**: Historical price patterns, technical indicators, support/resistance levels, and trading volume analysis |
|
|
3. **Performance Review**: Past trading decisions, performance evaluation, and lessons learned from previous reports |
|
|
|
|
|
## Workflow Structure: |
|
|
- Start with file discovery to identify and categorize available data sources |
|
|
- Perform the three analyses in parallel where possible for efficiency |
|
|
- Compile all findings into a comprehensive trading report |
|
|
|
|
|
## Agent Guidelines: |
|
|
- Agents should use appropriate tools to discover and read files from the data folder |
|
|
- Each analysis should focus on its specific domain without overlap |
|
|
- Agents should filter out irrelevant files and focus on data relevant to their analysis |
|
|
- All analysis must be based on actual data from files - no fake or estimated data |
|
|
- Present complete data without omissions or truncations |
|
|
|
|
|
## Report Structure: |
|
|
The final report should include: |
|
|
1. **Background Analysis**: Market environment and external factors |
|
|
2. **Price Analysis**: Technical patterns and indicators |
|
|
3. **Performance Review**: Historical performance and lessons learned |
|
|
4. **Trading Recommendations**: Specific buy/sell/hold decisions with quantities and prices |
|
|
|
|
|
## Critical Requirements: |
|
|
- Base all analysis on actual data read from files |
|
|
- If no relevant files are found, report this clearly and do not make up data |
|
|
- Provide specific trading recommendations with quantities and price targets |
|
|
- Consider current positions and available capital in decision making |
|
|
- Structure the report with clear sections and data tables |
|
|
- Return complete analysis without summarization |
|
|
""" |
|
|
|
|
|
|
|
|
def get_directories(stock_code, timestamp): |
|
|
"""Get directory paths for a given stock code and timestamp""" |
|
|
base_dir = Path(f"./{stock_code}") |
|
|
data_dir = base_dir / timestamp / "data" |
|
|
report_dir = base_dir / "reports" |
|
|
graphs_dir = base_dir / timestamp / "graphs" |
|
|
return base_dir, data_dir, report_dir, graphs_dir |
|
|
|
|
|
|
|
|
def check_data_exists(data_dir): |
|
|
"""Check if data files already exist in the data directory""" |
|
|
if not data_dir.exists(): |
|
|
return False |
|
|
|
|
|
|
|
|
expected_files = [ |
|
|
"stock_daily_catl_*.csv", |
|
|
"china_cpi_*.csv", |
|
|
"china_gdp_yearly_*.csv", |
|
|
"industry_fund_flow_*.csv", |
|
|
"stock_news_catl_*.csv", |
|
|
"market_summary_sse_*.csv", |
|
|
"market_indices_*.csv", |
|
|
"option_volatility_50etf_*.csv", |
|
|
"institution_recommendation_catl_*.csv" |
|
|
] |
|
|
|
|
|
existing_files = list(data_dir.glob("*.csv")) |
|
|
if len(existing_files) >= 5: |
|
|
print(f"✅ 数据文件已存在: {data_dir}") |
|
|
print(f" 发现 {len(existing_files)} 个数据文件") |
|
|
return True |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
def check_charts_exist(graphs_dir, stock_code): |
|
|
"""Check if chart files already exist""" |
|
|
if not graphs_dir.exists(): |
|
|
return False |
|
|
|
|
|
expected_charts = [ |
|
|
f"{stock_code}_technical_charts.png", |
|
|
f"{stock_code}_candlestick_chart.png" |
|
|
] |
|
|
|
|
|
existing_charts = [f.name for f in graphs_dir.glob("*.png")] |
|
|
if all(chart in existing_charts for chart in expected_charts): |
|
|
print(f"✅ 图表文件已存在: {graphs_dir}") |
|
|
print(f" 发现 {len(existing_charts)} 个图表文件") |
|
|
return True |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
def generate_workflow(): |
|
|
"""Generate a new workflow (commented out for future use)""" |
|
|
|
|
|
wf_generator = WorkFlowGenerator(llm=llm, tools=tools) |
|
|
workflow_graph: WorkFlowGraph = wf_generator.generate_workflow(goal=WORKFLOW_GOAL, retry=5) |
|
|
workflow_graph.save_module(module_save_path) |
|
|
return workflow_graph |
|
|
|
|
|
|
|
|
def execute_workflow(stock_code, data_dir, report_dir, timestamp): |
|
|
"""Execute the workflow with the given parameters""" |
|
|
try: |
|
|
|
|
|
workflow_graph: WorkFlowGraph = WorkFlowGraph.from_file(module_save_path) |
|
|
agent_manager = AgentManager(tools=tools) |
|
|
agent_manager.add_agents_from_workflow(workflow_graph, llm_config=llm.config) |
|
|
workflow = WorkFlow(graph=workflow_graph, agent_manager=agent_manager, llm=llm) |
|
|
workflow.init_module() |
|
|
|
|
|
|
|
|
output_file = report_dir / f"text_report_{stock_code}_{timestamp}.md" |
|
|
past_report = report_dir / f"text_report_{stock_code}_{timestamp}_previous.md" |
|
|
|
|
|
goal = f"""I need a daily trading decision for stock {stock_code}. |
|
|
Available funds: {available_funds} RMB |
|
|
Current positions: {current_positions} shares of {stock_code} at average price {average_price} RMB |
|
|
Date: {report_date} |
|
|
Type of position: {position_type} |
|
|
Data folder: {data_dir} |
|
|
Past report folder: {past_report} |
|
|
|
|
|
Please read ALL files in the data folder and generate a comprehensive trading decision report in Chinese based on real data. Return the complete content. |
|
|
""" |
|
|
|
|
|
output = workflow.execute({"goal": goal}) |
|
|
try: |
|
|
with open(output_file, "w", encoding="utf-8") as f: |
|
|
f.write(output) |
|
|
print(f"Trading decision report saved to: {output_file}") |
|
|
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error saving report: {e}") |
|
|
except Exception as e: |
|
|
print(f"Error executing workflow: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
|
|
|
|
|
|
def generate_html_report(stock_code, base_dir, report_dir, graphs_dir, timestamp): |
|
|
"""Generate HTML report from markdown and charts""" |
|
|
try: |
|
|
|
|
|
from html_report_generator import HTMLGenerator |
|
|
|
|
|
|
|
|
md_file = report_dir / f"text_report_{stock_code}_{timestamp}.md" |
|
|
html_output = base_dir/ datetime.now().strftime('%Y%m%d') / "html_report" / f"report_{stock_code}_{timestamp}.html" |
|
|
|
|
|
|
|
|
technical_chart = graphs_dir / f"{stock_code}_technical_charts.png" |
|
|
price_volume_chart = graphs_dir / f"{stock_code}_candlestick_chart.png" |
|
|
|
|
|
|
|
|
if not md_file.exists(): |
|
|
print(f"❌ Markdown file not found: {md_file}") |
|
|
return False |
|
|
|
|
|
|
|
|
if not technical_chart.exists(): |
|
|
print(f"⚠️ Technical chart not found: {technical_chart}") |
|
|
technical_chart = "" |
|
|
|
|
|
if not price_volume_chart.exists(): |
|
|
print(f"⚠️ Price/volume chart not found: {price_volume_chart}") |
|
|
price_volume_chart = "" |
|
|
|
|
|
|
|
|
print(f"[4] 生成HTML报告: {html_output}") |
|
|
generator = HTMLGenerator(str(html_output)) |
|
|
output_file = generator.generate_report( |
|
|
str(md_file), |
|
|
str(technical_chart) if technical_chart else "", |
|
|
str(price_volume_chart) if price_volume_chart else "" |
|
|
) |
|
|
|
|
|
print(f"✅ HTML报告生成成功: {output_file}") |
|
|
print(f"📁 资源文件夹: {Path(output_file).parent / 'assets'}") |
|
|
print(f"🌐 在浏览器中打开HTML文件查看报告") |
|
|
|
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
print(f"❌ HTML报告生成失败: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
return False |
|
|
|
|
|
|
|
|
def generate_html_from_existing_files(stock_code, timestamp=None): |
|
|
"""Generate HTML report from existing markdown and chart files""" |
|
|
if timestamp is None: |
|
|
timestamp = datetime.now().strftime('%Y%m%d') |
|
|
|
|
|
base_dir, data_dir, report_dir, graphs_dir = get_directories(stock_code, timestamp) |
|
|
|
|
|
print(f"🔍 查找现有文件:") |
|
|
print(f" 报告目录: {report_dir}") |
|
|
print(f" 图表目录: {graphs_dir}") |
|
|
|
|
|
|
|
|
if not report_dir.exists(): |
|
|
print(f"❌ 报告目录不存在: {report_dir}") |
|
|
return False |
|
|
|
|
|
if not graphs_dir.exists(): |
|
|
print(f"⚠️ 图表目录不存在: {graphs_dir}") |
|
|
graphs_dir = None |
|
|
|
|
|
return generate_html_report(stock_code, base_dir, report_dir, graphs_dir, timestamp) |
|
|
|
|
|
|
|
|
def main(): |
|
|
if len(sys.argv) < 2: |
|
|
stock_code = input("请输入股票代码 (如300750): ").strip() |
|
|
else: |
|
|
stock_code = sys.argv[1].strip() |
|
|
if not stock_code.isdigit(): |
|
|
print("❌ 股票代码应为数字!") |
|
|
return |
|
|
|
|
|
|
|
|
timestamp = datetime.now().strftime('%Y%m%d') |
|
|
base_dir, data_dir, report_dir, graphs_dir = get_directories(stock_code, timestamp) |
|
|
data_dir.mkdir(parents=True, exist_ok=True) |
|
|
report_dir.mkdir(parents=True, exist_ok=True) |
|
|
graphs_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
|
|
|
if not check_data_exists(data_dir): |
|
|
print(f"\n[1] 拉取数据到: {data_dir}") |
|
|
fetch_stock_data(stock_code, output_dir=str(data_dir)) |
|
|
else: |
|
|
print(f"\n[1] 跳过数据拉取 (数据已存在)") |
|
|
|
|
|
|
|
|
if not check_charts_exist(graphs_dir, stock_code): |
|
|
print(f"[2] 生成图表到: {graphs_dir}") |
|
|
generate_stock_charts(stock_code, output_dir=str(graphs_dir)) |
|
|
else: |
|
|
print(f"[2] 跳过图表生成 (图表已存在)") |
|
|
|
|
|
|
|
|
print(f"[3] 生成报告到: {report_dir}") |
|
|
|
|
|
execute_workflow(stock_code, data_dir, report_dir, timestamp) |
|
|
|
|
|
|
|
|
print(f"\n[4] 生成HTML报告") |
|
|
html_success = generate_html_report(stock_code, base_dir, report_dir, graphs_dir, timestamp) |
|
|
|
|
|
if html_success: |
|
|
print("\n✅ 全部流程完成!包括HTML报告生成") |
|
|
else: |
|
|
print("\n✅ 主要流程完成!(HTML报告生成失败)") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |
|
|
|