Model去掉footer

博客介绍了去掉iView Modal组件中取消和确定按钮的方法。Modal对话框可通过solt自定义页头、页脚、右上角关闭内容,实现去除按钮可通过自定义页脚,有两种方式,一是slot = “footer”,二是:footer - hide = “true”。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

去掉iview Modal组件中的取消和确定按钮

如下图所示:
iView-Model
Modal对话框可通过solt 自定义页头 / 页脚 / 右上角关闭内容 (header / footer / close)

实现去除“取消” 和“确定”按钮可通过自定页脚,其代码如下:

  1. 方式一:slot = “footer”
<Modal
        v-model = "modal1"
        title = "Common Modal dialog box title">
        <p>Content of dialog</p>
        <div slot="footer"><div>
    </Modal>  
  1. 方式二::footer-hide = “true”
<Modal
        v-model = "modal1"
        title = "Common Modal dialog box title"
        :footer-hide = "true">
        <p>Content of dialog</p>
    </Modal>  
链接: iView
API:Model(部分)

Modal props #

属性说明类型默认值
footer-hide不显示底部Booleanfalse

Modal slot #

名称说明
footer自定义页脚内容
<el-dialog :title="editingIndex === -1 ? '新增字段' : '编辑字段'" :visible.sync="fieldEditorVisible" width="600px"> <el-form label-width="100px"> <!-- 字段类型选择 --> <el-form-item label="字段类型"> <el-select v-model="tempField.type" @change="handleTypeChange"> <el-option label="输入框" value="el-input"/> <el-option label="下拉框" value="el-select"/> </el-select> </el-form-item> <!-- 基础属性 --> <el-form-item label="字段标签" required> <el-input v-model="tempField.label"/> </el-form-item> <el-form-item label="绑定模型" required> <el-input v-model="tempField.model" :disabled="editingIndex !== -1"> <template slot="append"> {{ tempField.model | camelCase }} </template> </el-input> </el-form-item> <!-- 下拉框选项(仅select类型显示) --> <template v-if="tempField.type === 'el-select'"> <el-divider>选项配置</el-divider> <draggable v-model="tempField.options"> <div v-for="(opt, index) in tempField.options" :key="index"> <el-input v-model="opt.label" placeholder="显示文本" style="width: 200px"/> <el-input v-model="opt.value" placeholder="存储值" style="width: 200px; margin-left: 10px"/> <el-button @click="removeOption(index)" icon="el-icon-delete" circle></el-button> </div> </draggable> <el-button @click="addOption">+ 添加选项</el-button> </template> </el-form> <div slot="footer"> <el-button @click="fieldEditorVisible = false">取消</el-button> <el-button type="primary" @click="saveField">保存</el-button> </div> </el-dialog>在修改字段的label时候会自动地把值同步给value且value不可被用户选中修改详细代码
03-14
<template> <el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" v-dialogDrag :visible.sync="visible"> <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="110px" style="margin-top: -0.275rem;background-color: #fff;border: 0px solid #DCDFE6;"> <el-form-item label="电池编号" prop="hwid"> <el-input :disabled="true" v-model="dataForm.hwid" placeholder="电池编号"></el-input> </el-form-item> <el-form-item label="电芯类型" prop="batteryCellId"> <!-- <el-input v-model="dataForm.batteryCellId" placeholder="电芯类型id"></el-input> --> <el-select v-model="dataForm.batteryCellId" placeholder="选择电芯类型"> <el-option v-for="item in batteryCellIds" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </el-form-item> <el-form-item label="电池类型" prop="batteryKind"> <el-select v-model="dataForm.batteryKind" placeholder="选择电池类型"> <el-option v-for="item in batteryKinds" :key="item.value" :label="item.label" :value="item.value" > </el-option> </el-select> </el-form-item> <el-form-item label="电芯封装" prop="batteryCellPackageId"> <!-- <el-input v-model="dataForm.batteryCellPackageId" placeholder="电芯封装id"></el-input> --> <el-select v-model="dataForm.batteryCellPackageId" placeholder="选择电芯封装"> <el-option v-for="item in batteryCellPackageIds" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </el-form-item> <el-form-item label="电池串数" prop="batteryBunchs"> <el-input v-model="dataForm.batteryBunchs" placeholder="电池串数"></el-input> </el-form-item> <el-form-item label="电池并数" prop="batteryUnions"> <el-input v-model="dataForm.batteryUnions" placeholder="电池并数"></el-input> </el-form-item> <el-form-item label="单体电压(V)" prop="singleBatteryVoltage"> <el-input v-model="dataForm.singleBatteryVoltage" placeholder="单体电压"></el-input> </el-form-item> <el-form-item label="额定总压(V)" prop="ratedTotalPressure"> <el-input v-model="dataForm.ratedTotalPressure" placeholder="额定总压"></el-input> </el-form-item> <el-form-item label="额定电流(A)" prop="ratedCurrent"> <el-input v-model="dataForm.ratedCurrent" placeholder="额定电流"></el-input> </el-form-item> <el-form-item label="额定容量(Ah)" prop="ratedCapacity"> <el-input v-model="dataForm.ratedCapacity" placeholder="额定容量"></el-input> </el-form-item> <el-form-item label="额定能量(kWh)" prop="ratedEnergy"> <el-input v-model="dataForm.ratedEnergy" placeholder="额定能量"></el-input> </el-form-item> <el-form-item label="电池箱数" prop="batteryQuantity"> <el-input v-model="dataForm.batteryQuantity" placeholder="电池箱数"></el-input> </el-form-item> <el-form-item label="电池厂商" prop="batteryFirm"> <el-input v-model="dataForm.batteryFirm" placeholder="电池厂商"></el-input> </el-form-item> <el-form-item label="激活日期" prop="activationDate"> <!-- <el-input v-model="dataForm.activationDate" placeholder="激活日期"></el-input> --> <el-date-picker v-model="dataForm.activationDate" type="date" :disabled="true" placeholder="选择日期"> </el-date-picker> </el-form-item> <el-form-item label="BMS编号" prop="bmsNum"> <el-input v-model="dataForm.bmsNum" placeholder="BMS编号"></el-input> </el-form-item> <el-form-item label="电芯编码" prop="batteryCellNum"> <el-input v-model="dataForm.batteryCellNum" placeholder="电芯编码"></el-input> </el-form-item> <el-form-item label="标称容量(Ah)" prop="nominalCapacity"> <el-input v-model="dataForm.nominalCapacity" placeholder="标称容量"></el-input> </el-form-item> <el-form-item label="系统状态" prop="sysStatus"> <!-- <el-input v-model="dataForm.sysStatus" placeholder="系统状态:1 充电,2 放电,3 搁置,4空载"></el-input> --> <el-select v-model="dataForm.sysStatus" placeholder="系统状态"> <el-option v-for="item in sysStatuses" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </el-form-item> <el-form-item label="警报状态" prop="alarmStatus"> <!-- <el-input v-model="dataForm.alarmStatus" placeholder="警报状态:0:正常,1:报警"></el-input> --> <el-select v-model="dataForm.alarmStatus" placeholder="报警状态"> <el-option v-for="item in alarmStatuses" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </el-form-item> <el-form-item label="所属项目" prop="projectId"> <!-- <el-input v-model="dataForm.projectId" placeholder="所属项目"></el-input> --> <el-select v-model="dataForm.projectId" placeholder="所属项目"> <el-option v-for="item in projectIds" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </el-form-item> <el-form-item label="城市" prop="cityId" style="display: flex;flex-direction: row;"> <!-- <el-input v-model="dataForm.cityId" placeholder=""></el-input> --> <div style="margin-left: -110px;"> <el-select v-model="dataForm.proviceId" placeholder="省份" @change="handleChange"> <el-option v-for="item in provices" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> <el-select v-model="dataForm.cityId" placeholder="城市" @change="handleChange2"> <el-option v-for="item in cities" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> <el-select v-model="dataForm.districtId" placeholder="区县"> <el-option v-for="item in districts" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </div> </el-form-item> <el-form-item label="软件版本" prop="softVersion"> <el-input v-model="dataForm.softVersion" placeholder="软件版本"></el-input> </el-form-item> <el-form-item label="硬件版本" prop="hardwareVersion"> <el-input v-model="dataForm.hardwareVersion" placeholder="硬件版本"></el-input> </el-form-item> <el-form-item label="动力类型" prop="powerTypeId"> <!-- <el-input v-model="dataForm.powerTypeId" placeholder="动力类型"></el-input> --> <el-select v-model="dataForm.powerTypeId" placeholder="选择动力类型"> <el-option v-for="item in powerTypeIds" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </el-form-item> <el-form-item label="二维码" prop="qrCode"> <el-input v-model="dataForm.qrCode" placeholder="二维码"></el-input> </el-form-item> <el-form-item label="SCID" prop="scid"> <el-input v-model="dataForm.scid" placeholder="SCID"></el-input> </el-form-item> <el-form-item label="PLMN" prop="plmn"> <el-input v-model="dataForm.plmn" placeholder="PLMN"></el-input> </el-form-item> <el-form-item label="LAC" prop="lac"> <el-input v-model="dataForm.lac" placeholder="LAC"></el-input> </el-form-item> <el-form-item label="CID" prop="cid"> <el-input v-model="dataForm.cid" placeholder="CID"></el-input> </el-form-item> <el-form-item label="IMEI" prop="imei"> <el-input v-model="dataForm.imei" placeholder="IMEI"></el-input> </el-form-item> <el-form-item label="ICCID" prop="iccid"> <el-input v-model="dataForm.iccid" placeholder="ICCID"></el-input> </el-form-item> <el-form-item label="CCID" prop="ccid"> <el-input v-model="dataForm.ccid" placeholder="CCID"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="visible = false">取消</el-button> <el-button type="primary" @click="dataFormSubmit()">确定</el-button> </span> </el-dialog> </template> <script> export default { data () { return { visible: false, dataForm: { id: 0, hwid: '', batteryCellId: [], batteryKind: [], batteryCellPackageId: [], batteryBunchs: '', batteryUnions: '', singleBatteryVoltage: '', ratedTotalPressure: '', ratedCurrent: '', ratedCapacity: '', ratedEnergy: '', batteryQuantity: '', batteryFirm: '', activationDate: '', bmsNum: '', batteryCellNum: '', batteryChassisNum: '', nominalCapacity: '', sysStatus: '', alarmStatus: '', projectId: '', cityId: '', softVersion: '', hardwareVersion: '', powerTypeId: '', qrCode: '', scid: '', plmn: '', lac: '', cid: '', imei: '', iccid: '', ccid: '', // sysStatus: [], // cityId: [], districtId:[], proviceId:[], // tempproviceId: '', // softVersion: '', // alarmStatus: [], // projectId: [], // powerTypeId: [] }, dataRule: { hwid: [ { required: true, message: '电池编号不能为空', trigger: 'blur' } ], batteryCellId: [ { required: true, message: '电芯类型id不能为空', trigger: 'blur' } ], batteryKind: [ { required: true, message: '电池类型id不能为空', trigger: 'blur' } ], batteryCellPackageId: [ { required: true, message: '电芯封装id不能为空', trigger: 'blur' } ], batteryBunchs: [ { required: true, message: '电池串数不能为空', trigger: 'blur' } ], batteryUnions: [ { required: true, message: '电池并数不能为空', trigger: 'blur' } ], singleBatteryVoltage: [ { required: true, message: '单体电压不能为空', trigger: 'blur' } ], ratedTotalPressure: [ { required: true, message: '额定总压不能为空', trigger: 'blur' } ], ratedCurrent: [ { required: true, message: '额定电流不能为空', trigger: 'blur' } ], ratedCapacity: [ { required: true, message: '额定容量不能为空', trigger: 'blur' } ], ratedEnergy: [ { required: true, message: '额定能量不能为空', trigger: 'blur' } ], batteryQuantity: [ { required: true, message: '电池箱数不能为空', trigger: 'blur' } ], batteryFirm: [ { required: true, message: '电池厂商不能为空', trigger: 'blur' } ], activationDate: [ { required: true, message: '激活日期不能为空', trigger: 'blur' } ], bmsNum: [ { required: true, message: 'BMS编号不能为空', trigger: 'blur' } ], batteryCellNum: [ { required: true, message: '电芯编码不能为空', trigger: 'blur' } ], // batteryChassisNum: [ // { required: true, message: '底盘编号不能为空', trigger: 'blur' } // ], nominalCapacity: [ { required: true, message: '标称容量不能为空', trigger: 'blur' } ], capacity: [ { required: true, message: '容量不能为空', trigger: 'blur' } ], // createTime: [ // { required: true, message: '不能为空', trigger: 'blur' } // ], // updateTime: [ // { required: true, message: '更新时间不能为空', trigger: 'blur' } // ] sysStatus: [ { required: true, message: '系统状态不能为空', trigger: 'blur' } ], // alarmStatus: [ // { required: true, message: '警报状态不能为空', trigger: 'blur' } // ], // projectId: [ // { required: true, message: '所属项目不能为空', trigger: 'blur' } // ], // cityId: [ // { required: true, message: '城市不能为空', trigger: 'blur' } // ], softVersion: [ { required: true, message: '软件版本不能为空', trigger: 'blur' } ], // hardwareVersion: [ // { required: true, message: '硬件版本不能为空', trigger: 'blur' } // ], powerTypeId: [ { required: true, message: '动力类型不能为空', trigger: 'blur' } ], // qrCode: [ // { required: true, message: '二维码不能为空', trigger: 'blur' } // ], // scid: [ // { required: true, message: 'scid不能为空', trigger: 'blur' } // ], // plmn: [ // { required: true, message: 'plmn不能为空', trigger: 'blur' } // ], // lac: [ // { required: true, message: 'lac不能为空', trigger: 'blur' } // ], // cid: [ // { required: true, message: 'cid不能为空', trigger: 'blur' } // ], // imei: [ // { required: true, message: 'imei不能为空', trigger: 'blur' } // ], // iccid: [ // { required: true, message: 'iccid不能为空', trigger: 'blur' } // ], // ccid: [ // { required: true, message: 'ccid不能为空', trigger: 'blur' } // ] }, batteryCellIds: [], batteryKinds:[{ value: 0, label: '运营' },{ value: 1, label: '售后' },{ value: 2, label: '内测' },{ value: 3, label: '报废' }], batteryCellPackageIds: [], provices: [{ value: '选项1', label: '黄金糕' }, { value: '选项2', label: '双皮奶', disabled: true }, { value: '选项3', label: '蚵仔煎' }, { value: '选项4', label: '龙须面' }, { value: '选项5', label: '北京烤鸭' }], value:'', cities: [], districts: [], sysStatuses: [], powerTypeIds: [], projectIds:[], customIds:[], alarmStatuses:[] } }, methods: { init (id) { this.dataForm.id = id || 0 this.visible = true this.$nextTick(() => { this.$refs['dataForm'].resetFields() if (this.dataForm.id) { this.$http({ url: this.$http.adornUrl(`/battery/bmsbatteryinfo/info/${this.dataForm.id}`), method: 'get', params: this.$http.adornParams() }).then(({data}) => { if (data && data.code === 0) { this.provices = data.data.provices this.cities = data.data.cities this.districts = data.data.districts this.sysStatuses = data.data.sysStatuses this.powerTypeIds = data.data.powers this.alarmStatuses = data.data.alarmStatuses this.projectIds = data.data.projectIds this.batteryCellIds = data.data.batteryCellIds this.batteryCellPackageIds = data.data.batteryCellPackageIds const socitem = data.data.bmsBatteryInfo // this.dataForm.batteryNum = data.bmsBatteryInfo.batteryNum this.dataForm.hwid = socitem.hwid this.dataForm.batteryCellId = socitem.batteryCellId this.dataForm.batteryKind = socitem.batteryKind this.dataForm.batteryCellPackageId = socitem.batteryCellPackageId this.dataForm.batteryBunchs = socitem.batteryBunchs this.dataForm.batteryUnions = socitem.batteryUnions this.dataForm.singleBatteryVoltage = socitem.singleBatteryVoltage this.dataForm.ratedTotalPressure = socitem.ratedTotalPressure this.dataForm.ratedCurrent = socitem.ratedCurrent this.dataForm.ratedCapacity = socitem.ratedCapacity this.dataForm.ratedEnergy = socitem.ratedEnergy this.dataForm.batteryQuantity = socitem.batteryQuantity this.dataForm.batteryFirm = socitem.batteryFirm this.dataForm.activationDate = socitem.activationDate this.dataForm.bmsNum = socitem.bmsNum this.dataForm.batteryCellNum = socitem.batteryCellNum // this.dataForm.batteryChassisNum = socitem.batteryChassisNum this.dataForm.nominalCapacity = socitem.nominalCapacity // this.dataForm.capacity = socitem.capacity this.dataForm.sysStatus = socitem.sysStatus this.dataForm.alarmStatus = socitem.alarmStatus this.dataForm.projectId = socitem.projectId this.dataForm.cityId = socitem.cityId this.dataForm.districtId = socitem.districtId this.dataForm.proviceId = socitem.proviceId this.dataForm.softVersion = socitem.softVersion this.dataForm.hardwareVersion = socitem.hardwareVersion this.dataForm.powerTypeId = socitem.powerTypeId this.dataForm.qrCode = socitem.qrCode this.dataForm.scid = socitem.scid this.dataForm.plmn = socitem.plmn this.dataForm.lac = socitem.lac this.dataForm.cid = socitem.cid this.dataForm.imei = socitem.imei this.dataForm.iccid = socitem.iccid this.dataForm.ccid = socitem.ccid this.dataForm.createTime = socitem.createTime this.dataForm.updateTime = socitem.updateTime } }) }else{ this.$http({ url: this.$http.adornUrl(`/battery/bmsbatteryinfo/getmenus`), method: 'get', params: this.$http.adornParams() }).then(({data}) => { if (data && data.code === 0) { this.provices = data.data.provices // this.cities = data.data.cities // this.districts = data.data.districts this.sysStatuses = data.data.sysStatuses this.powerTypeIds = data.data.powers this.alarmStatuses = data.data.alarmStatuses this.projectIds = data.data.projectIds this.batteryCellIds = data.data.batteryCellIds this.batteryCellPackageIds = data.data.batteryCellPackageIds } }) } }) }, mouseblur() { // alert("鼠标失去焦点,检查HWID是否重复"); }, handleChange(value) { // alert("鼠标"+value); this.dataForm.cityId = '' this.dataForm.districtId = '' this.getCities(value, 2) }, handleChange2(value) { // alert("鼠标===="+value); this.dataForm.districtId = '' this.getCities(value, 3) }, getCities(pid, level) { this.$http({ url: this.$http.adornUrl('/battery/bmsdistrictarea/getCitiesByPid'), method: 'get', params: this.$http.adornParams({ 'level': level, 'pid': pid }) }).then(({data}) => { if (data && data.code === 0) { if(level==1){ this.provices = data.data } if(level==2){ this.cities = data.data } if(level==3){ this.districts = data.data } } else { this.provices = [] this.cities = [] this.districts = [] } }) }, // 表单提交 dataFormSubmit () { this.$refs['dataForm'].validate((valid) => { if (valid) { this.$http({ url: this.$http.adornUrl(`/battery/bmsbatteryinfo/${!this.dataForm.id ? 'save' : 'update'}`), method: 'post', data: this.$http.adornData({ 'id': this.dataForm.id || undefined, 'hwid': this.dataForm.hwid, 'batteryCellId': this.dataForm.batteryCellId, 'batteryKind': this.dataForm.batteryKind, 'batteryCellPackageId': this.dataForm.batteryCellPackageId, 'batteryBunchs': this.dataForm.batteryBunchs, 'batteryUnions': this.dataForm.batteryUnions, 'singleBatteryVoltage': this.dataForm.singleBatteryVoltage, 'ratedTotalPressure': this.dataForm.ratedTotalPressure, 'ratedCurrent': this.dataForm.ratedCurrent, 'ratedCapacity': this.dataForm.ratedCapacity, 'ratedEnergy': this.dataForm.ratedEnergy, 'batteryQuantity': this.dataForm.batteryQuantity, 'batteryFirm': this.dataForm.batteryFirm, // 'activationDate': this.dataForm.activationDate, 'activateDateFormat': this.dataForm.activationDate, 'bmsNum': this.dataForm.bmsNum, 'batteryCellNum': this.dataForm.batteryCellNum, // 'batteryChassisNum': this.dataForm.batteryChassisNum, 'nominalCapacity': this.dataForm.nominalCapacity, // 'capacity': this.dataForm.capacity, 'sysStatus': this.dataForm.sysStatus, 'alarmStatus': this.dataForm.alarmStatus, 'projectId': this.dataForm.projectId, 'cityId': this.dataForm.cityId, 'districtId': this.dataForm.districtId, 'proviceId': this.dataForm.proviceId, 'softVersion': this.dataForm.softVersion, 'hardwareVersion': this.dataForm.hardwareVersion, 'powerTypeId': this.dataForm.powerTypeId, 'qrCode': this.dataForm.qrCode, 'scid': this.dataForm.scid, 'plmn': this.dataForm.plmn, 'lac': this.dataForm.lac, 'cid': this.dataForm.cid, 'imei': this.dataForm.imei, 'iccid': this.dataForm.iccid, 'ccid': this.dataForm.ccid, 'createTime': this.dataForm.createTime, 'updateTime': this.dataForm.updateTime }) }).then(({data}) => { if (data && data.code === 0) { this.$message({ message: '操作成功', type: 'success', duration: 1500, onClose: () => { this.visible = false this.$emit('refreshDataList') } }) } else { this.$message.error(data.msg) } }) } }) } } } </script> 优化此页面结构和ui,横向分模块展示
07-23
基于下列代码进行上述要求的修改 # -*- coding: utf-8 -*- """ Created on Sun Jul 20 22:17:32 2025 @author: srx20 """ # -*- coding: utf-8 -*- """ 股票预测筛选程序 - 支持自定义排名范围输出并屏蔽特定股票 """ import os import joblib import pandas as pd import numpy as np from tqdm import tqdm from typing import Dict, List, Tuple from sklearn.preprocessing import StandardScaler from sklearn.cluster import MiniBatchKMeans import talib as ta import logging from datetime import datetime import matplotlib.pyplot as plt from matplotlib import font_manager as fm import base64 from io import BytesIO # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 # 设置日志记录 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('stock_prediction_filter.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) # ========== 配置类 ========== class StockConfig: def __init__(self): # 数据路径 self.SH_PATH = r"D:\股票量化数据库\股票csv数据\上证" self.SZ_PATH = r"D:\股票量化数据库\股票csv数据\深证" # 聚类设置 self.CLUSTER_NUM = 8 self.CLUSTER_FEATURES = [ 'price_change', 'volatility', 'volume_change', 'MA5', 'MA20', 'RSI14', 'MACD_hist' ] # 目标条件 self.MIN_GAIN = 0.05 self.MIN_LOW_RATIO = 0.98 # 预测特征 (初始列表,实际使用时会动态更新) self.PREDICT_FEATURES = [ 'open', 'high', 'low', 'close', 'volume', 'price_change', 'volatility', 'volume_change', 'MA5', 'MA20', 'RSI14', 'MACD_hist', 'cluster', 'MOM10', 'ATR14', 'VWAP', 'RSI_diff', 'price_vol_ratio', 'MACD_RSI', 'advance_decline', 'day_of_week', 'month' ] # 需要屏蔽的股票前缀 self.BLOCKED_PREFIXES = ['SZ_sz16', 'SH_sh88','SH_sh999','SH_sh688', 'SZ_sz300','SH_sh500'] # ========== 特征工程 ========== class FeatureEngineer: def __init__(self, config): self.config = config def safe_fillna(self, series, default=0): """安全填充NaN值""" if isinstance(series, pd.Series): return series.fillna(default) elif isinstance(series, np.ndarray): return np.nan_to_num(series, nan=default) return series def transform(self, df): """添加技术指标特征""" try: # 创建临时副本用于TA-Lib计算 df_temp = df.copy() # 将价格列转换为float64以满足TA-Lib要求 for col in ['open', 'high', 'low', 'close']: df_temp[col] = df_temp[col].astype(np.float64) # 基础特征 df['price_change'] = df['close'].pct_change().fillna(0) df['volatility'] = df['close'].rolling(5).std().fillna(0) df['volume_change'] = df['volume'].pct_change().fillna(0) df['MA5'] = df['close'].rolling(5).mean().fillna(0) df['MA20'] = df['close'].rolling(20).mean().fillna(0) # 技术指标 rsi = ta.RSI(df_temp['close'].values, timeperiod=14) df['RSI14'] = self.safe_fillna(rsi, 50) macd, macd_signal, macd_hist = ta.MACD( df_temp['close'].values, fastperiod=12, slowperiod=26, signalperiod=9 ) df['MACD_hist'] = self.safe_fillna(macd_hist, 0) # 新增特征 mom = ta.MOM(df_temp['close'].values, timeperiod=10) df['MOM10'] = self.safe_fillna(mom, 0) atr = ta.ATR( df_temp['high'].values, df_temp['low'].values, df_temp['close'].values, timeperiod=14 ) df['ATR14'] = self.safe_fillna(atr, 0) # 成交量加权平均价 vwap = (df['volume'] * (df['high'] + df['low'] + df['close']) / 3).cumsum() / df['volume'].cumsum() df['VWAP'] = self.safe_fillna(vwap, 0) # 相对强弱指数差值 df['RSI_diff'] = df['RSI14'] - df['RSI14'].rolling(5).mean().fillna(0) # 价格波动比率 df['price_vol_ratio'] = df['price_change'] / (df['volatility'].replace(0, 1e-8) + 1e-8) # 技术指标组合特征 df['MACD_RSI'] = df['MACD_hist'] * df['RSI14'] # 市场情绪指标 df['advance_decline'] = (df['close'] > df['open']).astype(int).rolling(5).sum().fillna(0) # 时间特征 df['day_of_week'] = df['date'].dt.dayofweek df['month'] = df['date'].dt.month # 处理无穷大和NaN df = df.replace([np.inf, -np.inf], np.nan) df = df.fillna(0) return df except Exception as e: logger.error(f"特征工程失败: {str(e)}", exc_info=True) # 返回基本特征作为回退方案 df['price_change'] = df['close'].pct_change().fillna(0) df['volatility'] = df['close'].rolling(5).std().fillna(0) df['volume_change'] = df['volume'].pct_change().fillna(0) df['MA5'] = df['close'].rolling(5).mean().fillna(0) df['MA20'] = df['close'].rolling(20).mean().fillna(0) # 填充缺失的技术指标 for col in self.config.PREDICT_FEATURES: if col not in df.columns: df[col] = 0 return df # ========== 聚类模型 ========== class StockCluster: def __init__(self, config): self.config = config self.scaler = StandardScaler() self.kmeans = MiniBatchKMeans( n_clusters=config.CLUSTER_NUM, random_state=42, batch_size=1000 ) self.cluster_map = {} # 股票代码到聚类ID的映射 self.model_file = "stock_cluster_model.pkl" # 模型保存路径 def load(self): """从文件加载聚类模型""" if os.path.exists(self.model_file): model_data = joblib.load(self.model_file) self.kmeans = model_data['kmeans'] self.scaler = model_data['scaler'] self.cluster_map = model_data['cluster_map'] logger.info(f"从 {self.model_file} 加载聚类模型") return True else: logger.warning("聚类模型文件不存在") return False def transform(self, df, stock_code): """为数据添加聚类特征""" cluster_id = self.cluster_map.get(stock_code, -1) # 默认为-1表示未知聚类 df['cluster'] = cluster_id return df # ========== 数据加载函数 ========== def load_prediction_data(sh_path: str, sz_path: str, lookback_days: int = 30) -> Dict[str, pd.DataFrame]: """ 加载用于预测的股票数据(只加载最近lookback_days天的数据) """ stock_data = {} exchanges = [ ('SH', sh_path), ('SZ', sz_path) ] total_files = 0 for exchange, path in exchanges: if os.path.exists(path): csv_files = [f for f in os.listdir(path) if f.endswith('.csv')] total_files += len(csv_files) if total_files == 0: logger.warning("没有找到任何CSV文件") return stock_data pbar = tqdm(total=total_files, desc='加载股票数据') for exchange, path in exchanges: if not os.path.exists(path): continue for file in os.listdir(path): if not file.endswith('.csv'): continue stock_code = f"{exchange}_{file.split('.')[0]}" file_path = os.path.join(path, file) try: # 读取整个文件 df = pd.read_csv(file_path) # 验证必要的列是否存在 required_cols = ['date', 'open', 'high', 'low', 'close', 'volume'] if not all(col in df.columns for col in required_cols): logger.debug(f"股票 {stock_code} 缺少必要列,跳过") pbar.update(1) continue # 转换日期并排序 df['date'] = pd.to_datetime(df['date']) df = df.sort_values('date', ascending=False) # 只取最近lookback_days天的数据 if len(df) > lookback_days: df = df.head(lookback_days) # 转换数据类型 for col in ['open', 'high', 'low', 'close']: df[col] = pd.to_numeric(df[col], errors='coerce').astype(np.float32) df['volume'] = pd.to_numeric(df['volume'], errors='coerce').astype(np.uint32) # 删除包含NaN的行 df = df.dropna(subset=required_cols) if len(df) > 0: stock_data[stock_code] = df logger.debug(f"成功加载股票 {stock_code},数据条数: {len(df)}") else: logger.warning(f"股票 {stock_code} 无有效数据") except Exception as e: logger.error(f"加载股票 {stock_code} 失败: {str(e)}", exc_info=True) pbar.update(1) pbar.close() logger.info(f"成功加载 {len(stock_data)} 只股票数据") return stock_data # ========== 生成HTML报告 ========== def generate_html_report(top_stocks: List[Tuple[str, float]], prediction_date: str, model_version: str = "1.0", start_rank: int = 1, end_rank: int = 50, blocked_count: int = 0) -> str: """ 生成HTML格式的预测报告 参数: top_stocks: 包含(股票代码, 概率)元组的列表 prediction_date: 预测日期 model_version: 模型版本号 start_rank: 起始排名 end_rank: 结束排名 blocked_count: 屏蔽的股票数量 返回: HTML字符串 """ # 创建DataFrame df = pd.DataFrame(top_stocks, columns=['股票代码', '上涨概率']) df['排名'] = range(start_rank, start_rank + len(df)) # 创建技术指标图表 plt.figure(figsize=(10, 6)) plt.bar(df['股票代码'], df['上涨概率'], color='skyblue') plt.title(f'Top {start_rank}-{end_rank}股票上涨概率分布', fontsize=16) plt.xlabel('股票代码', fontsize=12) plt.ylabel('上涨概率', fontsize=12) plt.xticks(rotation=90, fontsize=8) plt.ylim(0.7, 1.0) plt.grid(axis='y', linestyle='--', alpha=0.7) # 将图表转换为Base64编码 buf = BytesIO() plt.savefig(buf, format='png', bbox_inches='tight') buf.seek(0) chart_base64 = base64.b64encode(buf.read()).decode('utf-8') plt.close() # 生成HTML内容 html_content = f""" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>大涨小跌预测结果 ({start_rank}-{end_rank}名)</title> <style> body {{ font-family: 'Microsoft YaHei', sans-serif; margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; }} .container {{ max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 30px; }} .header {{ text-align: center; padding-bottom: 20px; border-bottom: 1px solid #eee; margin-bottom: 30px; }} .header h1 {{ color: #1e3a8a; margin-bottom: 10px; }} .header .subtitle {{ color: #6b7280; font-size: 18px; }} .info-box {{ background-color: #f0f7ff; border-left: 4px solid #3b82f6; padding: 15px; margin-bottom: 30px; border-radius: 0 5px 5px 0; }} .chart-container {{ text-align: center; margin-bottom: 30px; }} .chart-container img {{ max-width: 100%; border-radius: 5px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); }} table {{ width: 100%; border-collapse: collapse; margin-bottom: 30px; }} th, td {{ padding: 12px 15px; text-align: center; border-bottom: 1px solid #e5e7eb; }} th {{ background-color: #3b82f6; color: white; font-weight: bold; }} tr:nth-child(even) {{ background-color: #f9fafb; }} tr:hover {{ background-color: #f0f7ff; }} .footer {{ text-align: center; padding-top: 20px; border-top: 1px solid #eee; color: #6b7280; font-size: 14px; }} .highlight {{ color: #10b981; font-weight: bold; }} .rank-1 {{ background-color: #ffeb3b; }} .rank-2 {{ background-color: #e0e0e0; }} .rank-3 {{ background-color: #ff9800; }} .blocked-info {{ background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 10px; margin-top: 15px; border-radius: 0 5px 5px 0; }} </style> </head> <body> <div class="container"> <div class="header"> <h1>大涨小跌预测结果 ({start_rank}-{end_rank}名)</h1> <div class="subtitle">基于机器学习模型的股票预测分析</div> </div> <div class="info-box"> <p><strong>预测日期:</strong>{prediction_date}</p> <p><strong>模型版本:</strong>{model_version}</p> <p><strong>排名范围:</strong>{start_rank}-{end_rank}名</p> <p><strong>筛选条件:</strong>收盘价 > 开盘价 × 105% 且 最低价 > 开盘价 × 98%</p> <p><strong>屏蔽规则:</strong>已过滤掉特定前缀的股票(SZ_sz16, SH_sh88)</p> <p><strong>说明:</strong>本报告基于历史数据预测,不构成投资建议</p> </div> <div class="chart-container"> <h2>Top {start_rank}-{end_rank}股票上涨概率分布图</h2> <img src="data:image/png;base64,{chart_base64}" alt="股票上涨概率分布图"> </div> <h2>详细预测结果</h2> <table> <thead> <tr> <th>排名</th> <th>股票代码</th> <th>上涨概率</th> <th>预测评级</th> </tr> </thead> <tbody> """ # 添加表格行 for i, (stock_code, prob) in enumerate(top_stocks): rank = start_rank + i rating = "" row_class = "" if prob >= 0.95: rating = "⭐⭐⭐⭐⭐" elif prob >= 0.9: rating = "⭐⭐⭐⭐" elif prob >= 0.85: rating = "⭐⭐⭐" elif prob >= 0.8: rating = "⭐⭐" else: rating = "⭐" if rank == 1: row_class = "class='rank-1'" elif rank == 2: row_class = "class='rank-2'" elif rank == 3: row_class = "class='rank-3'" html_content += f""" <tr {row_class}> <td>{rank}</td> <td>{stock_code}</td> <td class="highlight">{prob:.4f}</td> <td>{rating}</td> </tr> """ # 添加HTML尾部 html_content += f""" </tbody> </table> <div class="blocked-info"> <p><strong>屏蔽信息:</strong>在预测过程中已过滤掉 {blocked_count} 只以特定前缀开头的股票</p> </div> <div class="footer"> <p>生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | 预测模型:LightGBM分类器</p> <p>© 2025 股票量化分析系统 | 本报告仅供研究参考</p> </div> </div> </body> </html> """ return html_content # ========== 主预测函数 ========== def predict_top_stocks(model_path: str = "stock_prediction_model.pkl", top_n: int = 50, start_rank: int = 1, end_rank: int = 50) -> List[Tuple[str, float]]: """ 预测满足条件的Top N股票并生成HTML报告 新增参数: start_rank: 起始排名 (包含) end_rank: 结束排名 (包含) """ # 验证排名范围 if start_rank < 1: logger.warning(f"起始排名不能小于1,已自动调整为1 (原值: {start_rank})") start_rank = 1 if end_rank < start_rank: logger.warning(f"结束排名不能小于起始排名,已自动调整为起始排名 (原值: end_rank={end_rank}, start_rank={start_rank})") end_rank = start_rank if end_rank > top_n: logger.warning(f"结束排名不能大于总排名数,已自动调整为最大排名数 (原值: end_rank={end_rank}, top_n={top_n})") end_rank = top_n # 1. 初始化配置 config = StockConfig() logger.info(f"===== 股票预测筛选程序 (排名范围: {start_rank}-{end_rank}) =====") logger.info(f"屏蔽规则: 将过滤掉前缀为 {config.BLOCKED_PREFIXES} 的股票") # 2. 加载模型 if not os.path.exists(model_path): logger.error(f"模型文件 {model_path} 不存在") return [] try: model, selected_features = joblib.load(model_path) logger.info(f"成功加载预测模型,使用特征: {selected_features}") except Exception as e: logger.error(f"加载模型失败: {str(e)}", exc_info=True) return [] # 3. 加载聚类模型 cluster_model = StockCluster(config) cluster_model_loaded = cluster_model.load() if not cluster_model_loaded: logger.warning("无法加载聚类模型,使用默认聚类") # 4. 加载股票数据(最近30天) logger.info("加载股票数据...") stock_data = load_prediction_data(config.SH_PATH, config.SZ_PATH, lookback_days=30) if not stock_data: logger.error("没有加载到任何股票数据") return [] # 5. 初始化特征工程 feature_engineer = FeatureEngineer(config) # 6. 准备预测数据 predictions = [] blocked_stocks = [] # 存储被屏蔽的股票 logger.info("处理股票数据并进行预测...") for stock_code, df in tqdm(stock_data.items(), desc="预测股票"): try: # 检查是否需要屏蔽该股票 if any(stock_code.startswith(prefix) for prefix in config.BLOCKED_PREFIXES): blocked_stocks.append(stock_code) continue # 确保数据按日期升序排列(用于正确计算指标) df = df.sort_values('date', ascending=True) # 特征工程 df = feature_engineer.transform(df.copy()) # 添加聚类特征 if cluster_model_loaded: df = cluster_model.transform(df, stock_code) # 获取最新一天的数据(用于预测) latest_data = df.iloc[-1:].copy() # 确保所有特征都存在 for feature in selected_features: if feature not in latest_data.columns: latest_data[feature] = 0 # 选择模型使用的特征 X_pred = latest_data[selected_features] # 预测概率(类别1的概率) proba = model.predict_proba(X_pred)[0, 1] # 添加到预测结果 predictions.append((stock_code, proba)) except Exception as e: logger.error(f"处理股票 {stock_code} 失败: {str(e)}", exc_info=True) # 记录屏蔽信息 blocked_count = len(blocked_stocks) logger.info(f"已屏蔽 {blocked_count} 只股票: {blocked_stocks}") # 7. 按概率排序并取指定排名范围 predictions.sort(key=lambda x: x[1], reverse=True) top_predictions = predictions[:top_n] # 先取全部top_n # 筛选指定排名范围 selected_predictions = top_predictions[start_rank-1:end_rank] # 列表索引从0开始 # 8. 生成HTML报告 prediction_date = datetime.now().strftime("%Y-%m-%d") html_content = generate_html_report(selected_predictions, prediction_date, start_rank=start_rank, end_rank=end_rank, blocked_count=blocked_count) # 9. 保存HTML报告 html_file = f"大涨小跌预测结果_{start_rank}-{end_rank}名.html" with open(html_file, "w", encoding="utf-8") as f: f.write(html_content) logger.info(f"已生成HTML报告: {html_file}") return selected_predictions if __name__ == "__main__": # 示例用法: # 获取第1-10名 #top_1_10 = predict_top_stocks(top_n=50, start_rank=1, end_rank=10) # 获取第11-30名 #top_11_30 = predict_top_stocks(top_n=50, start_rank=11, end_rank=30) # 获取第31-50名 #top_31_50 = predict_top_stocks(top_n=50, start_rank=31, end_rank=50) # 自定义范围 (例如第5-15名) custom_range = predict_top_stocks(top_n=50, start_rank=1, end_rank=50) # 保存所有CSV结果 results = [ #(top_1_10, "1-10"), #(top_11_30, "11-30"), #(top_31_50, "31-50"), (custom_range, "1-50") ] for result, range_name in results: if result: result_df = pd.DataFrame(result, columns=['股票代码', '上涨概率']) # 计算实际排名范围 actual_start = int(range_name.split('-')[0]) actual_end = actual_start + len(result) - 1 result_df['排名'] = range(actual_start, actual_end + 1) result_df.to_csv(f'大涨小跌预测结果_{range_name}名.csv', index=False, encoding='utf-8-sig') logger.info(f"结果已保存到 大涨小跌预测结果_{range_name}名.csv")
07-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值