基于Dify的股票分析工作流搭建

该文章已生成可运行项目,

来源:B站博主视频:Dify+Qwen3:基于Dify+Qwen3构建股票分析系统

通过观看他人视频以及自己结合大模型提问搭建出来,相关代码因为视频未给出,所以对于服务代码以及代码执行代码都是自己一点点调出来的,确保最后可以运行成功。接下来我会把所有相关配置以及注意点罗列出来,如果文档看不太明白可以结合博主视频来看。

目录

来源:B站博主视频:Dify+Qwen3:基于Dify+Qwen3构建股票分析系统

一、获取模型API

1.进入阿里云百炼

 二、配置模型API

三、进行工作流的搭建 

1.创建空白应用

2.开始结点配置

3.条件分支配置

​4.错误回复结点

5.变量聚合器

6. http请求结点

6.1首先我们添加环境变量

7.代码执行

8.大模型配置 

9.直接回复

​四、服务端代码

五、结果展示


工作流预览:

一、获取模型API

1.进入阿里云百炼

网址:https://bailian.console.aliyun.com/console?tab=model#/api-key

2.创建API

 二、配置模型API

1.进入模型供应商

2.添加新的API

三、进行工作流的搭建 

1.创建空白应用

应用类型选择chatflow:

2.开始结点配置

设置三个开始结点的三个输入参数:

 stockcode1:

注意:选择下拉选项可以配置参数选项

选项这里是添加了一些股票代码。避免用户每次使用都需要手动输入。

必填选项要取消对钩,便于选择其他输入方式

stockcode2:

自己仔细对照确保有遗漏设置,可以参考上面的注意点进行检查

 marketType:

具体字母代表的含义后续工作流里会有解释,现在只需要了解其代表不同的市场类型。

3.条件分支配置

注意:设置参数1和参数2为空并且它们之间是AND的关系。这样做是为了确保两种输入方式起码有一种被使用,否则说明没有输入股票代码,自然也就无法分析,所以有了后面的错误回复结点。

直接点击左侧的AND即可切换条件之间的关系:AND、OR

4.错误回复结点

5.变量聚合器

条件分支的else条件即连接变量聚合器用来接收我们开始结点输入的参数 

 6. http请求结点
6.1首先我们添加环境变量

6.2 API方式选择POST

遮挡部分切换成自己电脑的IP地址:win+r     cmd (命令行)    ipconfig查看

添加参数第二行的键值对的值后面的就是我们刚刚配置的环境变量

BODY格式选择JSON

内容:自己把{}里的内容切换

{ "stock_code": "{{{#1750756031219.output#}}}}",

"market_type": "{{{#1750755720522.marketType#}}}}",

"start_date": null,

"end_date": null } 

7.代码执行

输入输出变量进行如下配置

  执行代码:

import json
import requests

def preprocess_http_response(response):
    """
    预处理HTTP请求的响应,将JSON数据转换为所需的字符串格式
    
    Args:
        response: HTTP请求的响应对象
        
    Returns:
        dict: 包含三个字符串类型变量的字典
    """
    # 确保响应内容是字符串
    if isinstance(response, requests.Response):
        content = response.text
    elif isinstance(response, bytes):
        content = response.decode('utf-8')
    elif isinstance(response, dict):
        content = json.dumps(response)
    elif isinstance(response, str):
        content = response
    else:
        content = str(response)
    
    # 尝试解析JSON
    try:
        data = json.loads(content)
    except json.JSONDecodeError:
        # 如果解析失败,创建一个包含原始内容的默认结构
        data = {
            "technical_summary": content,
            "recent_data": "",
            "report": ""
        }
    
    # 提取所需数据并确保它们是字符串类型
    technical_summary = str(data.get("technical_summary", ""))
    recent_data = str(data.get("recent_data", ""))
    report = str(data.get("report", ""))
    
    return {
        "technical_summary": technical_summary,
        "recent_data": recent_data,
        "report": report
    }

def main(arg1: str) -> dict:  # 修改返回类型为dict
    print(arg1)
    # 假设arg1可能是JSON字符串或URL
    
    try:
        # 尝试作为JSON解析
        data = json.loads(arg1)
        result = preprocess_http_response(data)
    except json.JSONDecodeError:
        # 如果不是JSON,尝试作为URL处理
        try:
            if arg1.startswith(('http://', 'https://')):
                response = requests.get(arg1)
                result = preprocess_http_response(response)
            else:
                # 如果既不是JSON也不是URL,则作为普通字符串处理
                result = {
                    "technical_summary": arg1,
                    "recent_data": "",
                    "report": ""
                }
        except Exception as e:
            result = {
                "technical_summary": f"Error processing input: {str(e)}",
                "recent_data": "",
                "report": ""
            }
    
    # 直接返回字典而不是JSON字符串
    return result

8.条件分支

进行如下配置:

8.大模型配置 

配置基本相同,只需要换对应名称即可,名称就是大模型名称

9.直接回复

同理进行对于修改即可

 四、服务端代码

这部分代码放到pycharm里自己安装相关库然后运行后在dify里进行运行。

from fastapi import FastAPI, HTTPException, Depends, Header
from pydantic import BaseModel
from datetime import datetime, timedelta
import pandas as pd
import json
import akshare as ak
import os

app = FastAPI()

# 参数配置
params = {
    'ma_periods': {'short': 5, 'medium': 20, 'long': 60},
    'rsi_period': 14,
    'bollinger_period': 20,
    'bollinger_std': 2,
    'volume_ma_period': 20,
    'atr_period': 14
}

# 从环境变量获取API密钥
API_KEY = os.environ.get("自己环境配置的名称", "切换为自己的API_key")

# 鉴权 Token 验证
def verify_auth_token(authorization: str = Header(None)):
    """
    验证Authorization Header中的Bearer Token
    """
    print(authorization)
    if not authorization:
        raise HTTPException(status_code=401, detail="Missing Authorization Header")

    scheme, _, token = authorization.partition(" ")
    if scheme.lower() != "bearer":
        raise HTTPException(status_code=401, detail="Invalid Authorization scheme")

    # 使用环境变量中的API密钥进行验证
    if token != API_KEY:
        raise HTTPException(status_code=403, detail="Invalid or Expired Token")

    return token


class StockAnalysisRequest(BaseModel):
    stock_code: str
    market_type: str = 'A'
    start_date: str = None
    end_date: str = None


def calculate_score(df):
    """
    计算评分
    """
    try:
        score = 0
        latest = df.iloc[-1]

        # 趋势得分(30分)
        if latest['MA5'] > latest['MA20']:
            score += 15
        if latest['MA20'] > latest['MA60']:
            score += 15

        # RSI得分(20分)
        if 30 <= latest['RSI'] <= 70:
            score += 20
        elif latest['RSI'] < 30:  # 超卖
            score += 15

        # MACD得分(20分)
        if latest['MACD'] > latest['Signal']:
            score += 20

        # 成交量得分(30分)
        if latest['Volume_Ratio'] > 1.5:
            score += 30
        elif latest['Volume_Ratio'] > 1:
            score += 15

        return score
    except Exception as e:
        print(f"计算评分时出错: {str(e)}")
        raise


def calculate_indicators(df):
    """
    计算技术指标
    """
    try:
        # 计算移动平均线
        df['MA5'] = df['close'].rolling(window=params['ma_periods']['short']).mean()
        df['MA20'] = df['close'].rolling(window=params['ma_periods']['medium']).mean()
        df['MA60'] = df['close'].rolling(window=params['ma_periods']['long']).mean()

        # 计算RSI
        df['RSI'] = calculate_rsi(df['close'], params['rsi_period'])

        # 计算MACD
        df['MACD'], df['Signal'], df['MACD_hist'] = calculate_macd(df['close'])

        # 计算布林带
        df['BB_upper'], df['BB_middle'], df['BB_lower'] = calculate_bollinger_bands(
            df['close'],
            params['bollinger_period'],
            params['bollinger_std']
        )

        # 成交量分析
        df['Volume_MA'] = df['volume'].rolling(window=params['volume_ma_period']).mean()
        df['Volume_Ratio'] = df['volume'] / df['Volume_MA']

        # 计算ATR和波动率
        df['ATR'] = calculate_atr(df, params['atr_period'])
        df['Volatility'] = df['ATR'] / df['close'] * 100

        # 动量指标
        df['ROC'] = df['close'].pct_change(periods=10) * 100

        return df
    except Exception as e:
        print(f"计算技术指标时出错: {str(e)}")
        raise


def _truncate_json_for_logging(json_obj, max_length=500):
    """裁断JSON对象用于日志记录,免日志过大"""
    json_str = json.dumps(json_obj)
    if len(json_str) <= max_length:
        return json_str
    return json_str[:max_length] + "..."


def get_stock_data(stock_code, market_type='A', start_date=None, end_date=None):
    """
    获取股票历史数据
    """
    try:
        # 设置默认日期
        if not end_date:
            end_date = datetime.now().strftime('%Y-%m-%d')
        if not start_date:
            start_date = (datetime.now() - timedelta(days=120)).strftime('%Y-%m-%d')

        # 根据市场类型获取数据
        if market_type == 'A':
            df = ak.stock_zh_a_hist(
                symbol=stock_code,
                start_date=start_date,
                end_date=end_date,
                adjust="qfq"
            )
        elif market_type == 'HK':
            df = ak.stock_hk_daily(
                symbol=stock_code,
                adjust="qfq"
            )
        elif market_type == 'US':
            df = ak.stock_us_hist(
                symbol=stock_code,
                start_date=start_date,
                end_date=end_date,
                adjust="qfq"
            )
        elif market_type == 'ETF':
            df = ak.fund_etf_hist_em(
                symbol=stock_code,
                period="daily",
                start_date=start_date,
                end_date=end_date,
                adjust="qfq"
            )
        elif market_type == 'LOF':
            df = ak.fund_lof_hist_em(
                symbol=stock_code,
                period="daily",
                start_date=start_date,
                end_date=end_date,
                adjust="qfq"
            )
        else:
            raise ValueError(f"不支持的市场类型: {market_type}")

        # 统一列名
        df = df.rename(columns={
            "日期": "date",
            "开盘": "open",
            "收盘": "close",
            "最高": "high",
            "最低": "low",
            "成交量": "volume"
        })

        # 确保日期格式正确
        df['date'] = pd.to_datetime(df['date'])

        # 数据类型转换
        numeric_columns = ['open', 'close', 'high', 'low', 'volume']
        df[numeric_columns] = df[numeric_columns].apply(pd.to_numeric, errors='coerce')

        # 删除空值
        df = df.dropna()

        return df.sort_values('date')
    except Exception as e:
        raise Exception(f"获取数据失败: {str(e)}")


def calculate_ema(series, period):
    """
    计算指数移动平均线
    """
    return series.ewm(span=period, adjust=False).mean()


def calculate_rsi(series, period):
    """
    计算RSI指标
    """
    delta = series.diff()
    gain = delta.where(delta > 0, 0).fillna(0)
    loss = -delta.where(delta < 0, 0).fillna(0)

    avg_gain = gain.rolling(window=period).mean()
    avg_loss = loss.rolling(window=period).mean()

    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))

    return rsi


def calculate_macd(series, fast=12, slow=26, signal=9):
    """
    计算MACD指标
    """
    ema_fast = calculate_ema(series, fast)
    ema_slow = calculate_ema(series, slow)
    macd_line = ema_fast - ema_slow
    signal_line = calculate_ema(macd_line, signal)
    histogram = macd_line - signal_line

    return macd_line, signal_line, histogram


def calculate_bollinger_bands(series, period, std_dev):
    """
    计算布林带
    """
    middle = series.rolling(window=period).mean()
    std = series.rolling(window=period).std()
    upper = middle + std_dev * std
    lower = middle - std_dev * std

    return upper, middle, lower


def calculate_atr(df, period):
    """
    计算ATR (Average True Range)
    """
    high = df['high']
    low = df['low']
    close = df['close'].shift(1)

    tr1 = high - low
    tr2 = abs(high - close)
    tr3 = abs(low - close)

    tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)

    return tr.rolling(window=period).mean()


def get_recommendation(score):
    """
    根据得分给出建议
    """
    if score >= 80:
        return '强烈推荐买入'
    elif score >= 60:
        return '建议买入'
    elif score >= 40:
        return '观望'
    elif score >= 20:
        return '建议卖出'
    else:
        return '强烈建议卖出'


@app.post("/analyze-stock/")
async def analyze_stock(request: StockAnalysisRequest, auth_token: str = Depends(verify_auth_token)):
    try:
        # 获取股票数据
        stock_data = get_stock_data(
            request.stock_code,
            request.market_type,
            request.start_date,
            request.end_date
        )

        print(stock_data)

        # 计算技术指标
        stock_data = calculate_indicators(stock_data)

        # 计算评分
        score = calculate_score(stock_data)

        # 提取最新数据和前一天数据
        latest = stock_data.iloc[-1]
        prev = stock_data.iloc[-2] if len(stock_data) > 1 else latest

        # 生成技术分析摘要
        technical_summary = {
            "moving_averages": {
                "MA5": latest['MA5'],
                "MA20": latest['MA20'],
                "MA60": latest['MA60'],
                "trend": "上升" if latest['MA5'] > latest['MA20'] else "下降"
            },
            "oscillators": {
                "RSI": latest['RSI'],
                "MACD": latest['MACD'],
                "Signal": latest['Signal'],
                "ROC": latest['ROC']
            },
            "volatility": {
                "ATR": latest['ATR'],
                "Volatility": latest['Volatility'],
                "BB_upper": latest['BB_upper'],
                "BB_lower": latest['BB_lower']
            },
            "volume": {
                "Volume": latest['volume'],
                "Volume_MA": latest['Volume_MA'],
                "Volume_Ratio": latest['Volume_Ratio']
            }
        }

        recent_data = stock_data.tail(14).to_dict('records')

        # 生成报告
        report = {
            "stock_code": request.stock_code,
            "market_type": request.market_type,
            "analysis_date": datetime.now().strftime('%Y-%m-%d'),
            "score": score,
            "price": latest['close'],
            "price_change": (latest['close'] - prev['close']) / prev['close'] * 100,
            "ma_trend": 'UP' if latest['MA5'] > latest['MA20'] else 'DOWN',
            "rsi": latest['RSI'] if not pd.isna(latest['RSI']) else None,
            "macd_signal": 'BUY' if latest['MACD'] > latest['Signal'] else 'SELL',
            "volume_status": 'HIGH' if latest['Volume_Ratio'] > 1.5 else 'NORMAL',
            "recommendation": get_recommendation(score)
        }

        # 返回结果
        return {
            "technical_summary": technical_summary,
            "recent_data": recent_data,
            "report": report
        }

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


if __name__ == '__main__':
    import uvicorn

    # 这里是您需要修改为自己IP的地方(第370行)
    # 将"0.0.0.0"替换为您的本地IP地址,或者保持"0.0.0.0"让服务监听所有网络接口
    # 端口号8020需要与您之前尝试连接的端口一致
    uvicorn.run(app, host="192.168.231.1", port=8020)

主函数里将地址修改为自己的IP地址

然后还有环境配置部分

这里根据环境配置进行修改:

把这两个填进去即可

不知道代码里是否会还有其他需要修改的,自己给大模型解决吧。

五、结果展示

本文章已经生成可运行项目
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值