import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import random
class ZhenanBottomTransaction:
"""镇安抽水蓄能电站兜底交易模拟 - 支持月度、周度、日度多级交易"""
def __init__(self, bottom_threshold=0.95):
"""
初始化电站参数
Parameters:
bottom_threshold: 触发兜底的电量比例阈值,默认0.95(95%)
"""
# 电站基本参数
self.name = "镇安抽水蓄能电站"
self.unit_capacity = 350 # 单机容量 MW
self.unit_count = 4 # 机组台数
self.total_capacity = self.unit_capacity * self.unit_count # 总装机容量 MW
self.min_gen_power = self.unit_capacity * 0.6 # 单机最小发电功率 MW
# 年设计电量 (亿kWh)
self.annual_pump_design = 31.2 # 年抽水电量
self.annual_gen_design = 23.4 # 年发电量
self.pump_gen_ratio = 4 / 3 # 抽水发电比例 4:3
# 权益省容量电费分摊比例
self.province_ratios = {
"陕西": 0.70,
"甘肃": 0.11,
"青海": 0.11,
"宁夏": 0.08
}
# 各省燃煤基准价 (元/kWh)
self.coal_base_prices = {
"陕西": 0.3545,
"甘肃": 0.3078,
"青海": 0.3896,
"宁夏": 0.2595
}
# 兜底交易价格系数
self.pump_price_ratio = 0.75 # 抽水价格为基准价的75%
# 兜底触发阈值 - 现在作为参数传入
self.bottom_threshold = bottom_threshold # 市场化交易满足比例不足时触发兜底
# 交易时段定义
self.peak_hours = [(7, 9), (18, 22)] # 峰段: 7:00-9:00, 18:00-22:00
self.flat_hours = [(0, 7), (9, 10), (16, 18), (22, 24)] # 平段
self.valley_hours = [(10, 16)] # 谷段: 10:00-16:00
# 存储市场化交易计划
self.market_transaction_schedule = {} # 存储每天的市场化交易计划
print(f"初始化完成 - 兜底触发阈值: {self.bottom_threshold:.1%}")
def set_bottom_threshold(self, threshold):
"""
设置兜底触发阈值
Parameters:
threshold: 触发兜底的电量比例 (0-1之间)
"""
if 0 <= threshold <= 1:
self.bottom_threshold = threshold
print(f"兜底触发阈值已更新为: {threshold:.1%}")
else:
print("错误: 阈值必须在0-1之间")
def calculate_monthly_design(self):
"""计算月度设计电量 (万kWh)"""
monthly_pump = self.annual_pump_design * 10000 / 12
monthly_gen = self.annual_gen_design * 10000 / 12
return monthly_pump, monthly_gen
def calculate_province_monthly_entitlement(self):
"""计算各省月度权益电量"""
monthly_pump, monthly_gen = self.calculate_monthly_design()
province_entitlement = {}
for province, ratio in self.province_ratios.items():
province_entitlement[province] = {
'pump_entitlement': monthly_pump * ratio,
'gen_entitlement': monthly_gen * ratio
}
return province_entitlement
def simulate_market_transactions(self, month):
"""模拟市场化交易,生成每天的交易计划"""
print(f"\n=== {month}月市场化交易模拟 ===")
days_in_month = 30 if month in [4, 6, 9, 11] else 31
province_entitlement = self.calculate_province_monthly_entitlement()
# 模拟市场化交易比例(年度+月度交易)
market_ratios = {
province: random.uniform(0.8, 1) for province in self.province_ratios.keys()
}#为每个省份随机生成市场化交易完成比例(80%-100%)
daily_market_schedule = {}
for day in range(1, days_in_month + 1):
date = f"{month:02d}-{day:02d}"
daily_market_schedule[date] = {
'hourly_pump': {hour: 0 for hour in range(24)},
'hourly_gen': {hour: 0 for hour in range(24)},
'total_pump': 0,
'total_gen': 0
}#按日期组织,每天包含24小时的详细计划,分别记录抽水和发电的小时级电量,汇总每日总量
# 为每个省份生成市场化交易计划
for province, ratio in market_ratios.items():
entitlement = province_entitlement[province]
daily_pump = (entitlement['pump_entitlement'] * ratio) / days_in_month
daily_gen = (entitlement['gen_entitlement'] * ratio) / days_in_month
# 生成小时级市场化交易曲线
hourly_pump = self._generate_market_hourly_pump_curve(daily_pump)
hourly_gen = self._generate_market_hourly_gen_curve(daily_gen)
# 累加到总市场化交易计划中
for hour in range(24):
daily_market_schedule[date]['hourly_pump'][hour] += hourly_pump[hour]
daily_market_schedule[date]['hourly_gen'][hour] += hourly_gen[hour]
daily_market_schedule[date]['total_pump'] += daily_pump
daily_market_schedule[date]['total_gen'] += daily_gen
self.market_transaction_schedule = daily_market_schedule
# 打印市场化交易结果
for province, ratio in market_ratios.items():
status = "需要兜底" if ratio < self.bottom_threshold else "已满足"
print(f"{province}: 市场化交易比例 {ratio:.1%}, 状态: {status}")
return market_ratios
def _generate_market_hourly_pump_curve(self, daily_energy):
"""生成市场化交易的抽水小时曲线"""
hourly_energy = {hour: 0 for hour in range(24)}
# 谷段小时 (10:00-16:00)
valley_hours = list(range(10, 16))
if daily_energy > 0:
# 计算需要运行的小时数(市场化交易不完全占用所有容量)
max_hours = min(len(valley_hours), 4) # 市场化交易最多运行4小时
hours_needed = min(max_hours, int(np.ceil(daily_energy / (self.total_capacity * 0.8))))
hours_needed = max(1, hours_needed)
# 在谷段内随机选择运行小时
operating_hours = random.sample(valley_hours, hours_needed)
hourly_energy_per_hour = daily_energy / hours_needed
for hour in operating_hours:
hourly_energy[hour] = hourly_energy_per_hour
return hourly_energy
def _generate_market_hourly_gen_curve(self, daily_energy):
"""生成市场化交易的发电小时曲线"""
hourly_energy = {hour: 0 for hour in range(24)}
# 峰段小时
peak_hours = []
for hour_range in self.peak_hours:
peak_hours.extend(range(hour_range[0], hour_range[1]))
if daily_energy > 0:
# 计算需要运行的小时数(市场化交易不完全占用所有容量)
max_hours = min(len(peak_hours), 4) # 市场化交易最多运行4小时
hours_needed = min(max_hours, int(np.ceil(daily_energy / (self.total_capacity * 0.8))))
hours_needed = max(1, hours_needed)
# 在峰段内随机选择运行小时
operating_hours = random.sample(peak_hours, hours_needed)
hourly_energy_per_hour = daily_energy / hours_needed
for hour in operating_hours:
hourly_energy[hour] = hourly_energy_per_hour
return hourly_energy
def calculate_weekly_remaining_capacity(self, month):
"""计算每周剩余容量(基于市场化交易)"""
print(f"\n--- {month}月周剩余容量计算 ---")
# 将月分为4周
weeks = [
{'days': range(1, 8), 'name': '第1周'},
{'days': range(8, 15), 'name': '第2周'},
{'days': range(15, 22), 'name': '第3周'},
{'days': range(22, 29), 'name': '第4周'}
]
weekly_capacities = []
for i, week in enumerate(weeks):
weekly_pump_used = 0
weekly_gen_used = 0
# 计算本周已使用的容量(市场化交易)
for day in week['days']:
date = f"{month:02d}-{day:02d}"
if date in self.market_transaction_schedule:
daily_schedule = self.market_transaction_schedule[date]
daily_pump_used = daily_schedule['total_pump']
daily_gen_used = daily_schedule['total_gen']
weekly_pump_used += daily_pump_used
weekly_gen_used += daily_gen_used
# 计算本周总容量
days_in_week = len(week['days'])
# 抽水总容量(谷段容量)
valley_hours_per_day = 6 # 10:00-16:00
weekly_pump_capacity = self.total_capacity * valley_hours_per_day * days_in_week / 10 # 万kWh
# 发电总容量(峰段+平段容量)
peak_flat_hours_per_day = 6 # 估算值
weekly_gen_capacity = self.total_capacity * peak_flat_hours_per_day * days_in_week * 0.75 / 10 # 万kWh,考虑效率
# 计算剩余容量
weekly_pump_remaining = max(0, weekly_pump_capacity - weekly_pump_used)
weekly_gen_remaining = max(0, weekly_gen_capacity - weekly_gen_used)
weekly_capacities.append({
'week': i + 1,
'name': week['name'],
'pump_capacity': weekly_pump_capacity,
'gen_capacity': weekly_gen_capacity,
'pump_used': weekly_pump_used,
'gen_used': weekly_gen_used,
'pump_remaining': weekly_pump_remaining,
'gen_remaining': weekly_gen_remaining,
'days': week['days']
})
#本周的容量信息(包括周次、周名、抽水总容量、发电总容量、已使用的抽水、已使用的发电、剩余抽水、剩余发电、包含的天数)
print(f"{week['name']}: 抽水剩余容量 {weekly_pump_remaining:.2f}万kWh, "
f"发电剩余容量 {weekly_gen_remaining:.2f}万kWh")
return weekly_capacities
def simulate_annual_trade(self, annual_ratio=0.8):
"""模拟年度交易
Parameters:
annual_ratio: 年度交易电量占权益电量的比例
"""
province_entitlement = self.calculate_province_monthly_entitlement()
annual_trade = {}
for province, entitlement in province_entitlement.items():
annual_trade[province] = {
'annual_pump': entitlement['pump_entitlement'] * annual_ratio,
'annual_gen': entitlement['gen_entitlement'] * annual_ratio
}
return annual_trade
def simulate_monthly_trade(self, annual_trade, monthly_ratio=0.15):
"""模拟月度交易
Parameters:
annual_trade: 年度交易结果
monthly_ratio: 月度交易电量占权益电量的比例
"""
province_entitlement = self.calculate_province_monthly_entitlement()
monthly_trade = {}
for province, entitlement in province_entitlement.items():
# 月度交易电量
monthly_pump = entitlement['pump_entitlement'] * monthly_ratio
monthly_gen = entitlement['gen_entitlement'] * monthly_ratio
# 年度+月度总交易电量
total_pump = annual_trade[province]['annual_pump'] + monthly_pump
total_gen = annual_trade[province]['annual_gen'] + monthly_gen
# 权益电量
pump_entitlement = entitlement['pump_entitlement']
gen_entitlement = entitlement['gen_entitlement']
# 覆盖比例 = 总交易电量 / 权益电量
monthly_trade[province] = {
'monthly_pump': monthly_pump,
'monthly_gen': monthly_gen,
'total_pump': total_pump,
'total_gen': total_gen,
'pump_entitlement': pump_entitlement,
'gen_entitlement': gen_entitlement,
'coverage_ratio': total_pump / pump_entitlement if pump_entitlement > 0 else 0
}
return monthly_trade
def identify_monthly_bottom_needs(self, monthly_trade):
"""识别需要月度兜底的省份和电量"""
province_bottom_needs = {}
bottom_provinces = []
for province, trade in monthly_trade.items():
coverage_ratio = trade['coverage_ratio']
if coverage_ratio < self.bottom_threshold:
# 需要月度兜底
pump_entitlement = trade['pump_entitlement']
gen_entitlement = trade['gen_entitlement']
target_pump = pump_entitlement * self.bottom_threshold# 目标抽水量
target_gen = gen_entitlement * self.bottom_threshold# 目标发电量
current_pump = trade['total_pump']
current_gen = trade['total_gen']
bottom_pump = max(0, target_pump - current_pump)# 所需抽水兜底
bottom_gen = max(0, target_gen - current_gen)# 所需发电兜底
province_bottom_needs[province] = {
'pump_need': bottom_pump,
'gen_need': bottom_gen,
'coverage_ratio': coverage_ratio,
'target_ratio': self.bottom_threshold,
'trade_type': 'monthly'
}
bottom_provinces.append(province)
return province_bottom_needs, bottom_provinces
def identify_intra_month_bottom_needs(self, monthly_trade):
"""识别需要月内兜底的剩余电量(100% - 阈值)"""
province_bottom_needs = {}
bottom_provinces = []
for province, trade in monthly_trade.items():
pump_entitlement = trade['pump_entitlement']
gen_entitlement = trade['gen_entitlement']
# 月内兜底目标:100%权益电量
target_pump = pump_entitlement
target_gen = gen_entitlement
current_pump = trade['total_pump']
current_gen = trade['total_gen']
bottom_pump = max(0, target_pump - current_pump)
bottom_gen = max(0, target_gen - current_gen)
if bottom_pump > 0:
province_bottom_needs[province] = {
'pump_need': bottom_pump,
'gen_need': bottom_gen,
'coverage_ratio': trade['coverage_ratio'],
'target_ratio': 1.0,
'trade_type': 'intra_month'
}
bottom_provinces.append(province)
return province_bottom_needs, bottom_provinces
def organize_monthly_bottom_trade(self, province_bottom_needs, bottom_provinces, month):
"""组织月度兜底交易 - 具体到天和小时"""
print(f"\n--- 月度兜底交易组织 (月份: {month}月) ---")
print(f"交易时间: 每月下旬签订下个月交易电量")
# 计算每月天数
if month in [1, 3, 5, 7, 8, 10, 12]:
days_in_month = 31
elif month in [4, 6, 9, 11]:
days_in_month = 30
else: # 2月
days_in_month = 28
monthly_transactions = {}
for province in bottom_provinces:
needs = province_bottom_needs[province]
# 计算日均电量
daily_pump = needs['pump_need'] / days_in_month
daily_gen = needs['gen_need'] / days_in_month
##均匀分配:将月度兜底电量平均分配到每一天
# 生成详细的日交易计划(具体到小时)
daily_schedules = []
for day in range(1, days_in_month + 1):
date = f"{month:02d}-{day:02d}"
schedule = self.generate_detailed_daily_schedule(
daily_pump, daily_gen, date, province, "monthly")
daily_schedules.append(schedule)
monthly_transactions[province] = {
'province': province,
'monthly_pump': needs['pump_need'],
'monthly_gen': needs['gen_need'],
'daily_pump': daily_pump,
'daily_gen': daily_gen,
'daily_schedules': daily_schedules,
'days_in_month': days_in_month,
'trade_type': 'monthly'
}
print(f"{province}: 月度兜底抽水{needs['pump_need']:.2f}万kWh, "
f"发电{needs['gen_need']:.2f}万kWh, 日均抽水{daily_pump:.2f}万kWh")
return monthly_transactions
def organize_weekly_bottom_trade(self, province_bottom_needs, bottom_provinces, month, weekly_capacities):
"""组织周度兜底交易 - 基于实际剩余容量"""
print(f"\n--- 周度兜底交易组织 (月份: {month}月) ---")
# 计算总需求和周容量
total_pump_needs = sum(needs['pump_need'] for needs in province_bottom_needs.values())
total_gen_needs = sum(needs['gen_need'] for needs in province_bottom_needs.values())
print(f"月内兜底总需求: 抽水 {total_pump_needs:.2f}万kWh, 发电 {total_gen_needs:.2f}万kWh")
for capacity in weekly_capacities:
print(f"{capacity['name']}: 抽水剩余容量 {capacity['pump_remaining']:.2f}万kWh, "
f"发电剩余容量 {capacity['gen_remaining']:.2f}万kWh")
# 分配策略:从后往前分配,确保后面周能满足需求
weekly_allocations = {province: [0] * 4 for province in bottom_provinces}
# 计算后三周总容量
last_three_weeks_pump = sum(w['pump_remaining'] for w in weekly_capacities[1:])
last_three_weeks_gen = sum(w['gen_remaining'] for w in weekly_capacities[1:])
# 判断第一周是否必须分配
must_allocate_week1 = (total_pump_needs > last_three_weeks_pump or
total_gen_needs > last_three_weeks_gen)
if must_allocate_week1:
print("第一周必须进行兜底分配(后三周容量不足)")
# 第一周按比例分配必须分配的电量
for province, needs in province_bottom_needs.items():
ratio = needs['pump_need'] / total_pump_needs
min_week1_pump = max(0, needs['pump_need'] - last_three_weeks_pump * ratio)
min_week1_gen = max(0, needs['gen_need'] - last_three_weeks_gen * ratio)
# 实际分配不超过周容量
actual_week1_pump = min(min_week1_pump, weekly_capacities[0]['pump_remaining'])
actual_week1_gen = min(min_week1_gen, weekly_capacities[0]['gen_remaining'])
weekly_allocations[province][0] = {
'pump': actual_week1_pump,
'gen': actual_week1_gen
}
weekly_capacities[0]['pump_remaining'] -= actual_week1_pump
weekly_capacities[0]['gen_remaining'] -= actual_week1_gen
# 分配剩余电量到各周(优先后几周)
remaining_needs = {}
for province, needs in province_bottom_needs.items():
allocated_pump = weekly_allocations[province][0]['pump'] if weekly_allocations[province][0] != 0 else 0
allocated_gen = weekly_allocations[province][0]['gen'] if weekly_allocations[province][0] != 0 else 0
remaining_needs[province] = {
'pump': needs['pump_need'] - allocated_pump,
'gen': needs['gen_need'] - allocated_gen
}
# 从第4周开始向前分配
for week_idx in [3, 2, 1, 0]: # 4,3,2,1周
if week_idx == 0 and not must_allocate_week1:
continue # 第一周不需要必须分配时跳过
for province, needs in remaining_needs.items():
if needs['pump'] > 0 and weekly_capacities[week_idx]['pump_remaining'] > 0:
# 按比例分配
total_remaining_pump = sum(n['pump'] for n in remaining_needs.values() if n['pump'] > 0)
if total_remaining_pump > 0:
allocation_ratio = min(1, weekly_capacities[week_idx]['pump_remaining'] / total_remaining_pump)
pump_allocation = needs['pump'] * allocation_ratio
gen_allocation = needs['gen'] * allocation_ratio
# 确保不超过周容量和剩余需求
pump_allocation = min(pump_allocation, needs['pump'],
weekly_capacities[week_idx]['pump_remaining'])
gen_allocation = min(gen_allocation, needs['gen'],
weekly_capacities[week_idx]['gen_remaining'])
if pump_allocation > 0:
if weekly_allocations[province][week_idx] == 0:
weekly_allocations[province][week_idx] = {'pump': 0, 'gen': 0}
weekly_allocations[province][week_idx]['pump'] += pump_allocation
weekly_allocations[province][week_idx]['gen'] += gen_allocation
remaining_needs[province]['pump'] -= pump_allocation
remaining_needs[province]['gen'] -= gen_allocation
weekly_capacities[week_idx]['pump_remaining'] -= pump_allocation
weekly_capacities[week_idx]['gen_remaining'] -= gen_allocation
# 生成周交易计划
weekly_transactions = {}
for province, allocations in weekly_allocations.items():
province_weekly_plans = []
for week_idx, allocation in enumerate(allocations):
if allocation != 0 and allocation['pump'] > 0:
# 计算周内日均电量
days_in_week = len(weekly_capacities[week_idx]['days'])
daily_pump = allocation['pump'] / days_in_week
daily_gen = allocation['gen'] / days_in_week
# 生成每日详细交易计划
daily_schedules = []
for day in weekly_capacities[week_idx]['days']:
date = f"{month:02d}-{day:02d}"
schedule = self.generate_detailed_daily_schedule(
daily_pump, daily_gen, date, province, "weekly")
daily_schedules.append(schedule)
week_plan = {
'week': week_idx + 1,
'name': weekly_capacities[week_idx]['name'],
'weekly_pump': allocation['pump'],
'weekly_gen': allocation['gen'],
'daily_pump': daily_pump,
'daily_gen': daily_gen,
'daily_schedules': daily_schedules,
'days': list(weekly_capacities[week_idx]['days'])
}
province_weekly_plans.append(week_plan)
if province_weekly_plans:
weekly_transactions[province] = {
'province': province,
'total_pump': province_bottom_needs[province]['pump_need'],
'total_gen': province_bottom_needs[province]['gen_need'],
'weekly_plans': province_weekly_plans,
'trade_type': 'weekly'
}
# 打印周度分配结果
print(f"\n{province}周度分配:")
for plan in province_weekly_plans:
print(f" {plan['name']}: 抽水{plan['weekly_pump']:.2f}万kWh, "
f"日均抽水{plan['daily_pump']:.2f}万kWh")
return weekly_transactions
def organize_daily_bottom_trade(self, province_bottom_needs, bottom_provinces, month):
"""组织日度兜底交易(D+3滚动) - 未来三天到月底"""
print(f"\n--- 日度兜底交易组织 (月份: {month}月) ---")
print("交易模式: D+3滚动交易(未来三天到月底)")
# 计算每月天数
if month in [1, 3, 5, 7, 8, 10, 12]:
days_in_month = 31
elif month in [4, 6, 9, 11]:
days_in_month = 30
else: # 2月
days_in_month = 28
daily_transactions = {}
for province in bottom_provinces:
needs = province_bottom_needs[province]
# 模拟D+3滚动交易过程
daily_plans = []
remaining_pump = needs['pump_need']
remaining_gen = needs['gen_need']
# 模拟每天的交易决策(从第1天开始,每3天一个交易周期)
for start_day in range(1, days_in_month + 1, 3):
trading_date = f"{month:02d}-{start_day:02d}"
# 计算从当前日到月底的剩余天数和剩余需求
remaining_days = days_in_month - start_day + 1
days_to_trade = min(3, remaining_days) # 本次交易的天数(最多3天)
if days_to_trade <= 0:
continue
# 计算这3天内的需求
daily_pump_needed = remaining_pump / remaining_days * days_to_trade
daily_gen_needed = remaining_gen / remaining_days * days_to_trade
# 考虑实际机组容量约束
max_daily_pump = self.total_capacity * 6 / 10 * days_to_trade # 谷段6小时容量
max_daily_gen = self.total_capacity * 10 / 10 * days_to_trade * 0.75 # 峰平段10小时容量,考虑效率
actual_daily_pump = min(daily_pump_needed, max_daily_pump)
actual_daily_gen = min(daily_gen_needed, max_daily_gen)
if actual_daily_pump > 0:
# 生成这3天的交易计划
for offset in range(days_to_trade):
current_day = start_day + offset
if current_day <= days_in_month:
date = f"{month:02d}-{current_day:02d}"
schedule = self.generate_detailed_daily_schedule(
actual_daily_pump / days_to_trade,
actual_daily_gen / days_to_trade,
date, province, "daily_D+3")
daily_plan = {
'trade_day': start_day,
'execution_day': current_day,
'daily_pump': actual_daily_pump / days_to_trade,
'daily_gen': actual_daily_gen / days_to_trade,
'schedule': schedule,
'trade_type': 'daily_D+3'
}
daily_plans.append(daily_plan)
# 更新剩余电量
remaining_pump -= actual_daily_pump
remaining_gen -= actual_daily_gen
print(f" D{start_day}交易: 交易未来{days_to_trade}天, "
f"抽水{actual_daily_pump:.2f}万kWh")
daily_transactions[province] = {
'province': province,
'total_pump': needs['pump_need'],
'total_gen': needs['gen_need'],
'daily_plans': daily_plans,
'trade_type': 'daily'
}
return daily_transactions
def generate_detailed_daily_schedule(self, daily_pump_energy, daily_gen_energy, date, province, trade_type):
"""
生成详细的日交易计划,具体到每个小时
改进:抽水时满抽,发电时不小于单机容量的60%
"""
# 生成小时级交易曲线
hourly_pump = self._generate_detailed_hourly_pump_curve(daily_pump_energy)
hourly_gen = self._generate_detailed_hourly_gen_curve(daily_gen_energy)
# 计算交易价格
base_price = self.coal_base_prices[province]
pump_price = base_price * self.pump_price_ratio
gen_price = base_price
# 计算日交易费用
daily_pump_cost = daily_pump_energy * 10000 * pump_price # 元
daily_gen_income = daily_gen_energy * 10000 * gen_price # 元
daily_net_cost = daily_pump_cost - daily_gen_income
# 创建详细的日交易计划
detailed_schedule = {
'date': date,
'province': province,
'trade_type': trade_type,
'daily_pump': daily_pump_energy,
'daily_gen': daily_gen_energy,
'hourly_pump': hourly_pump,
'hourly_gen': hourly_gen,
'pump_price': pump_price,
'gen_price': gen_price,
'daily_pump_cost': daily_pump_cost,
'daily_gen_income': daily_gen_income,
'daily_net_cost': daily_net_cost,
'is_balanced': True,
'pump_hours': self._get_operating_hours(hourly_pump),
'gen_hours': self._get_operating_hours(hourly_gen)
}
return detailed_schedule
def _generate_detailed_hourly_pump_curve(self, daily_energy):
"""生成详细的抽水小时曲线 - 满抽运行"""
hourly_energy = {hour: 0 for hour in range(24)}
# 谷段小时 (10:00-16:00)
valley_hours = list(range(10, 16))
if daily_energy > 0:
# 抽水时满抽运行
pump_power = self.total_capacity # MW, 满抽功率
# 计算需要运行的小时数
operating_hours = min(len(valley_hours), int(np.ceil(daily_energy * 10 / pump_power)))
operating_hours = max(1, operating_hours) # 至少运行1小时
if operating_hours > 0:
# 选择连续运行的谷段小时
start_hour = 10
selected_hours = valley_hours[:operating_hours]
# 每小时抽水电量 (万kWh)
hourly_energy_val = daily_energy / operating_hours
for hour in selected_hours:
hourly_energy[hour] = hourly_energy_val
print(f" 抽水运行: 满抽{pump_power}MW, 运行{operating_hours}小时, "
f"谷段{selected_hours[0]}:00-{selected_hours[-1] + 1}:00")
return hourly_energy
def _generate_detailed_hourly_gen_curve(self, daily_energy):
"""生成详细的发电小时曲线 - 不小于单机容量的60%"""
hourly_energy = {hour: 0 for hour in range(24)}
if daily_energy > 0:
# 发电功率约束:不小于单机容量的60%
min_gen_power = self.min_gen_power # MW
max_gen_power = self.total_capacity # MW
# 峰段小时
peak_hours = []
for hour_range in self.peak_hours:
peak_hours.extend(range(hour_range[0], hour_range[1]))
# 平段小时
flat_hours = []
for hour_range in self.flat_hours:
flat_hours.extend(range(hour_range[0], hour_range[1]))
available_hours = peak_hours + flat_hours
# 确定运行机组台数(满足最小发电功率约束)
if daily_energy * 10 <= min_gen_power: # 日发电量小于单机最小出力
# 单台机组运行,但需要满足最小出力
gen_power = min_gen_power
operating_units = 1
operating_hours = 1 # 集中运行1小时
else:
# 计算需要运行的小时数
operating_hours = min(len(available_hours), int(np.ceil(daily_energy * 10 / max_gen_power)))
operating_hours = max(1, operating_hours)
# 计算需要的发电功率
required_power = daily_energy * 10 / operating_hours
# 确定机组台数(满足最小出力约束)
operating_units = max(1, int(np.ceil(required_power / self.unit_capacity)))
gen_power = min(max_gen_power, operating_units * self.unit_capacity)
if operating_hours > 0:
# 优先在晚高峰连续运行
selected_hours = []
if 18 in available_hours:
# 从18点开始选择连续小时
start_idx = available_hours.index(18)
selected_hours = available_hours[start_idx:start_idx + operating_hours]
else:
# 选择可用的连续小时
selected_hours = available_hours[:operating_hours]
# 每小时发电电量 (万kWh)
hourly_energy_val = daily_energy / len(selected_hours)
for hour in selected_hours:
hourly_energy[hour] = hourly_energy_val
print(f" 发电运行: {operating_units}台机组, 功率{gen_power}MW, "
f"运行{operating_hours}小时, 时段{selected_hours[0]}:00-{selected_hours[-1] + 1}:00")
return hourly_energy
def _get_operating_hours(self, hourly_energy):
"""获取实际运行小时列表"""
operating_hours = []
current_block = []
for hour, energy in hourly_energy.items():
if energy > 0:
if not current_block or hour == current_block[-1] + 1:
current_block.append(hour)
else:
if current_block:
operating_hours.append(current_block)
current_block = [hour]
else:
if current_block:
operating_hours.append(current_block)
current_block = []
if current_block:
operating_hours.append(current_block)
return operating_hours
def run_comprehensive_simulation(self, month=6, annual_ratio=0.8, monthly_ratio=0.15):
"""运行综合兜底交易模拟"""
print("=" * 80)
print(f"镇安抽水蓄能电站综合兜底交易模拟 - {month}月")
print(f"兜底触发阈值: {self.bottom_threshold:.1%}")
print(f"年度交易比例: {annual_ratio:.1%}")
print(f"月度交易比例: {monthly_ratio:.1%}")
print("=" * 80)
# 0. 模拟市场化交易,生成交易计划和剩余容量
market_ratios = self.simulate_market_transactions(month)
# 1. 模拟年度交易
print("\n1. 年度交易模拟")
annual_trade = self.simulate_annual_trade(annual_ratio)
for province, trade in annual_trade.items():
print(f" {province}: 抽水{trade['annual_pump']:.2f}万kWh, "
f"发电{trade['annual_gen']:.2f}万kWh")
# 2. 模拟月度交易
print("\n2. 月度交易模拟")
monthly_trade = self.simulate_monthly_trade(annual_trade, monthly_ratio)
for province, trade in monthly_trade.items():
print(f" {province}: 覆盖率{trade['coverage_ratio']:.1%} "
f"(年度+月度: {trade['total_pump']:.1f}/{trade['pump_entitlement']:.1f}万kWh)")
# 3. 识别月度兜底需求
print("\n3. 月度兜底需求识别")
monthly_bottom_needs, monthly_bottom_provinces = self.identify_monthly_bottom_needs(monthly_trade)
if monthly_bottom_provinces:
print("需要月度兜底的省份:")
for province in monthly_bottom_provinces:
needs = monthly_bottom_needs[province]
print(f" {province}: 当前覆盖率{needs['coverage_ratio']:.1%}, "
f"需要兜底抽水{needs['pump_need']:.2f}万kWh")
# 组织月度兜底交易
monthly_transactions = self.organize_monthly_bottom_trade(
monthly_bottom_needs, monthly_bottom_provinces, month)
else:
print("所有省份年度+月度交易均已满足阈值,无需月度兜底")
monthly_transactions = {}
# 4. 计算周剩余容量
weekly_capacities = self.calculate_weekly_remaining_capacity(month)
# 5. 识别月内兜底需求
print("\n5. 月内兜底需求识别")
intra_month_bottom_needs, intra_month_bottom_provinces = self.identify_intra_month_bottom_needs(monthly_trade)
if intra_month_bottom_provinces:
print("需要月内兜底的省份:")
for province in intra_month_bottom_provinces:
needs = intra_month_bottom_needs[province]
print(f" {province}: 需要月内兜底抽水{needs['pump_need']:.2f}万kWh")
# 组织周度兜底交易
print("\n5.1 周度兜底方案")
weekly_transactions = self.organize_weekly_bottom_trade(
intra_month_bottom_needs, intra_month_bottom_provinces, month, weekly_capacities)
# 组织日度兜底交易
print("\n5.2 日度兜底方案 (D+3)")
daily_transactions = self.organize_daily_bottom_trade(
intra_month_bottom_needs, intra_month_bottom_provinces, month)
else:
print("所有省份交易均已满足100%,无需月内兜底")
weekly_transactions = {}
daily_transactions = {}
return {
'market_ratios': market_ratios,
'annual_trade': annual_trade,
'monthly_trade': monthly_trade,
'monthly_bottom': monthly_transactions,
'weekly_bottom': weekly_transactions,
'daily_bottom': daily_transactions,
'weekly_capacities': weekly_capacities,
'monthly_bottom_provinces': monthly_bottom_provinces,
'intra_month_bottom_provinces': intra_month_bottom_provinces
}
def print_detailed_schedule_example(self, simulation_result, province, day=15):
"""打印详细调度示例"""
print(f"\n--- {province}详细调度示例 (第{day}天) ---")
# 查找该省份的交易计划
date = f"06-{day:02d}"
# 优先查找月度兜底交易
if province in simulation_result['monthly_bottom']:
monthly_plan = simulation_result['monthly_bottom'][province]
for schedule in monthly_plan['daily_schedules']:
if schedule['date'] == date:
self._print_daily_schedule_details(schedule)
return
# 查找周度兜底交易
if province in simulation_result['weekly_bottom']:
weekly_plan = simulation_result['weekly_bottom'][province]
for week_plan in weekly_plan['weekly_plans']:
for schedule in week_plan['daily_schedules']:
if schedule['date'] == date:
self._print_daily_schedule_details(schedule)
return
# 查找日度兜底交易
if province in simulation_result['daily_bottom']:
daily_plan = simulation_result['daily_bottom'][province]
for plan in daily_plan['daily_plans']:
if plan['schedule']['date'] == date:
self._print_daily_schedule_details(plan['schedule'])
return
print(f"未找到{province}在{date}的交易计划")
def _print_daily_schedule_details(self, schedule):
"""打印日调度详情"""
print(f"日期: {schedule['date']}")
print(f"交易类型: {schedule['trade_type']}")
print(f"省份: {schedule['province']}")
print(f"日抽水电量: {schedule['daily_pump']:.2f}万kWh")
print(f"日发电电量: {schedule['daily_gen']:.2f}万kWh")
print(f"抽水价格: {schedule['pump_price']:.4f}元/kWh")
print(f"上网电价: {schedule['gen_price']:.4f}元/kWh")
# 打印抽水计划
print("\n抽水计划 (满抽运行):")
pump_blocks = schedule['pump_hours']
for block in pump_blocks:
if block:
print(f" {block[0]}:00-{block[-1] + 1}:00 | "
f"平均{schedule['hourly_pump'][block[0]]:.2f}万kWh/小时")
# 打印发电计划
print("\n发电计划 (满足最小出力约束):")
gen_blocks = schedule['gen_hours']
for block in gen_blocks:
if block:
print(f" {block[0]}:00-{block[-1] + 1}:00 | "
f"平均{schedule['hourly_gen'][block[0]]:.2f}万kWh/小时")
print(f"\n日交易费用:")
print(f" 抽水成本: {schedule['daily_pump_cost'] / 10000:.2f}万元")
print(f" 发电收入: {schedule['daily_gen_income'] / 10000:.2f}万元")
print(f" 净成本: {schedule['daily_net_cost'] / 10000:.2f}万元")
if __name__ == "__main__":
# 示例1: 使用默认阈值(95%)
print("场景1: 使用默认兜底触发阈值(95%)")
zhenan_default = ZhenanBottomTransaction(bottom_threshold=0.95)
result1 = zhenan_default.run_comprehensive_simulation(
month=6, annual_ratio=0.8, monthly_ratio=0.1)
# 打印详细调度示例
if result1['monthly_bottom_provinces']:
sample_province = result1['monthly_bottom_provinces'][0]
zhenan_default.print_detailed_schedule_example(result1, sample_province, 15)
print("\n" + "=" * 80)
print("=" * 80)
# 示例2: 使用更高的阈值(98%)
print("\n场景2: 使用更高的兜底触发阈值(98%)")
zhenan_high = ZhenanBottomTransaction(bottom_threshold=0.98)
result2 = zhenan_high.run_comprehensive_simulation(
month=6, annual_ratio=0.8, monthly_ratio=0.1)
print("\n" + "=" * 80)
print("=" * 80)
# 示例3: 动态调整阈值
print("\n场景3: 动态调整兜底触发阈值")
zhenan_dynamic = ZhenanBottomTransaction(bottom_threshold=0.90)
# 第一次模拟
print("\n第一次模拟 - 阈值90%:")
result3_1 = zhenan_dynamic.run_comprehensive_simulation(month=6)
# 调整阈值
zhenan_dynamic.set_bottom_threshold(0.98)
# 第二次模拟
print("\n第二次模拟 - 阈值98%:")
result3_2 = zhenan_dynamic.run_comprehensive_simulation(month=6)
在此代码的基础上进行改进,以下是我对此代码改进的要求:
默认中长期交易的时候,各省申报数据撮合完后形成的抽水电量是350mw的正整数倍,抽发比例4:3对应发电电量,年度交易签订时要求不能低于各省抽水权益电量的80%(对于抽水权益电量来说,电站年设计抽水电量是31.21亿千瓦时,但要考虑实际运行情况,现在我要求实际镇安抽蓄电站年抽水量是891712个*350mw,依据容量电费分摊比例陕西百分之七十,甘肃百分之十一,青海百分之十一,宁夏百分之八,那么一年中,陕西的权益抽水电量就是624199个*350mw,甘肃是98088个*350mw、青海是98088个*350mw、宁夏是71337个*350mw。根据抽发电量必须满足4:3的硬性约束对应的发电量也就确定了。)(每个小时有4个*350mw的电量)当加上月度交易电量时,要不少于权益抽水电量的95%,当小于95%时进行月度兜底交易,兜到95%,这95%设置成可调节的参数,月度兜底交易启动的阈值设置为可调节的参数,兜到相应的阈值(例如阈值为95%,则月度兜底交易兜到95%,而不是100%)。
在什么时段兜底:对于权益总抽水电量来说,理论上是一天中在谷段四台机组满抽六个小时(24个*350mw)的电量,但实际各省上报的抽水电量经过撮合时会有很多约束像抽水电量必须是350mw的整数倍,机组检修,抽水发电必须满足4:3,等。这样就会消减掉很多上报的抽水电量,所以在一天中有很大概率不是满抽六小时。那么对于在什么时段进行兜底来说就是首先在谷段那六个小时进行兜底,当谷段那六个小时全部抽满后还兜不完时那就可在9到10点、16到18这两个平段进行兜底。然后抽发4:3对应的权益发电量兜底在峰段,如果峰段没有剩余电量则在峰段附近的平段进行发电,避开抽水占用的平段。
兜多少:根据年度加月度的合同来看,在上个月的下旬就已经把下个月要交易的权益抽水电量签订好了,可以看出下个月交易了多少电量,有多少电量需要兜底。对于每个省每个月的权益抽水电量并不是平均分配的,而是每个是有自己每个月的电量分配系数,这个系数现在不知道,进行模拟,设置成可调节的参数,对陕西省来说一年的权益抽水电量是624199个*350mw,可这样设置每个月的分配系数:一月用624199个*350mw的8%、二月用10%、三月用11%、..........每个月的分配系数加在一起是100%。(这里有个约束每个月的权益抽水电量一定要是350mw的整数倍)。将月度权益抽水电量平均分配到每一天,会得到一个日权益抽水电量(也要是350mw的整数倍),用每一日权益抽水电量减去每一日实际交易的抽水电量,就会得到每一天需要兜底的电量,形成一个三十天的数据,具体到每一个小时,要这样显示:每个权益省的某个月1号的24个小时,每个小时交易了多少抽水电量,每个小时可进行兜底兜底的电量,对应的每个小时交易了多少发电电量(注意抽发不可在同一个小时进行,抽水在谷段和9到10点、16到18这两个平段;发电在峰段和峰段附近的平段,避开抽水的平段);2号的24个小时,每个小时交易了多少各单位的抽水电量,每个小时还有多少个可进行兜底兜底的电量,对应的每个小时交易了多少发电电量.......一直到30号。当补足谷段后月度的权益抽水电量还没兜完,那就要在九到十点、十六到十八点这三个小时进行兜底交易,补足这个月权益抽水电量的95%(阈值)。
详细完成此代码的修改并将数据生成在Excel表格里