Add market data handling, battery state updates, and unit tests

This commit is contained in:
2026-09-22 15:43:30 +01:00
parent 34a7aeaf03
commit 4e37339534
5 changed files with 139 additions and 2 deletions
+26
View File
@@ -0,0 +1,26 @@
import argparse
import datetime
from pathlib import Path
from .market import MarketState
from .battery import BatteryState
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--market1", type=Path, help="Market state file")
parser.add_argument("--market2", type=Path, help="Market state file")
args = parser.parse_args()
market1 = MarketState().load_market_data_from_csv(args.market1)
market2 = MarketState().load_market_data_from_csv(args.market2)
battery = BatteryState()
increment = datetime.timedelta(minutes=10)
market_1_start = market1.get_start_time()
market2_start = market2.get_start_time()
start_time = min(market_1_start, market2_start)
+51 -2
View File
@@ -1,6 +1,8 @@
import datetime
from enum import Enum
from batterymodel.models.market import MarketDataIncrement
class BatteryState(Enum):
IDLE = 0
@@ -19,12 +21,59 @@ class BatteryModel:
self._storage_volume_degradation_rate = 0.001 # %/cycle
self._fixed_operational_costs = 50000 # £/year
self._runtime = datetime.timedelta(0)
self._charge_discharge_cycles = 0
self._decision_log_file_handle = open("battery_log.txt", "w+")
self._battery_state = BatteryState.IDLE
self._current_charge_rate = 0.0
self._current_discharge_rate = 0.0
self._current_market_rate = 0.0
self._current_charge_value = 0.0
self._current_charge = 0.0
self._total_profit = 0.0
def update(self, increment: datetime.timedelta):
pass
def update(self, increment: datetime.timedelta, previous_state: "BatteryModel", market_data_increment: MarketDataIncrement):
pass
def handle_charge_or_discharge(self, increment: datetime.timedelta, previous_state: "BatteryModel"):
if self._battery_state != BatteryState.CHARGING:
total_charge_this_increment = self._current_charge_rate * (increment.total_seconds()/60.0)
self._current_charge = self._current_charge + total_charge_this_increment
# We filled the battery this increment
if self._current_charge > self._battery_capacity:
self._current_charge = self._battery_capacity
total_charge_this_increment = self._battery_capacity - previous_state._current_charge
self._battery_state = BatteryState.IDLE
time_of_charge = datetime.timedelta(seconds=total_charge_this_increment / self._current_charge_rate)
elif self._battery_state == BatteryState.DISCHARGING:
total_discharge_this_increment = self._current_discharge_rate * (increment.total_seconds() / 60.0)
self._current_charge = self._current_charge - total_discharge_this_increment
# emptied the battery this increment
if self._current_charge <= 0:
self._battery_state = BatteryState.IDLE
# might have to deal with penalties here for not providing enough?
time_of_charge = datetime.timedelta(seconds=previous_state._current_charge / self._current_discharge_rate)
if self._battery_state != BatteryState.IDLE:
self._current_charge_value = self._current_charge_value + (self._current_market_rate * time_of_charge.total_seconds())
self._total_profit += self._current_charge_value
self.log_state_change("handle_charge_or_discharge_charge", previous_state._current_charge, self._current_charge)
self.log_state_change("handle_charge_or_discharge_value", previous_state._current_charge_value,
self._current_charge_value)
def log_state_change(self, fn, original_state, new_state):
self._decision_log_file_handle.write(f"{self._runtime},{fn},{original_state},{new_state}\n")
def handle_storage_capacity_check(self, previous_state: "BatteryModel"):
if self._charge_discharge_cycles > previous_state._charge_discharge_cycles and self._charge_discharge_cycles / int(self._charge_discharge_cycles) == 1:
storage_volume_lost = (self._max_storage_volume/100) * self._storage_volume_degradation_rate
self._max_storage_volume -= storage_volume_lost
self.log_state_change("handle_storage_capacity_check_cycles_check", previous_state._max_storage_volume, self._max_storage_volume)
+40
View File
@@ -0,0 +1,40 @@
import csv
import datetime
from collections import OrderedDict
from typing import List, Tuple
from xmlrpc.client import DateTime
class MarketState:
def __init__(self):
self._market_data = OrderedDict()
def load_market_data_from_csv(self, csv_file):
with open(csv_file, 'r') as file:
reader = csv.reader(file)
for row in reader:
self._market_data[datetime.datetime.strptime(row[0], '%d-%m-%Y %H:%M')] = float(row[1])
def get_start_time(self):
return next(iter(self._market_data))
def get_market_rate_for_timestamp(self, timestamp: datetime.datetime):
last_timestamp = None
for key in self._market_data:
if last_timestamp is not None:
delta = timestamp - last_timestamp
key_delta = key - last_timestamp
if key == timestamp:
return self._market_data[key]
elif last_timestamp is not None and last_timestamp < key and key_delta > delta:
return self._market_data[last_timestamp]
else:
last_timestamp = key
return None
class MarketDataIncrement:
def __init__(self, timestamp: datetime.datetime, markets: List[Tuple[datetime.datetime, float]]):
self.timestamp = timestamp
self.markets = markets
+6
View File
@@ -1,4 +1,6 @@
from batterymodel import BatteryModel
from batterymodel.models.battery import BatteryState
def test_battery_stats():
assert BatteryModel()._max_charge_rate == 2000000 # Watts
@@ -10,3 +12,7 @@ def test_battery_stats():
assert BatteryModel()._lifetime_cycles == 50000
assert BatteryModel()._storage_volume_degradation_rate == 0.001 # %/cycle
assert BatteryModel()._fixed_operational_costs == 50000 # £/year
def test_battery_update():
battery = BatteryModel()
assert battery._battery_state == BatteryState.IDLE
+16
View File
@@ -0,0 +1,16 @@
import datetime
from batterymodel.models.market import MarketState
def test_market():
this_market = MarketState()
this_market._market_data[datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M')] = 11
this_market._market_data[datetime.datetime.strptime('01-01-2022 00:01', '%d-%m-%Y %H:%M')] = 12
this_market._market_data[datetime.datetime.strptime('01-01-2022 00:02', '%d-%m-%Y %H:%M')] = 13
this_market._market_data[datetime.datetime.strptime('01-01-2022 00:05', '%d-%m-%Y %H:%M')] = 14
assert this_market.get_market_rate_for_timestamp(datetime.datetime.strptime('01-01-2022 00:02', '%d-%m-%Y %H:%M')) == 13
assert this_market.get_market_rate_for_timestamp(datetime.datetime.strptime('01-01-2022 00:03', '%d-%m-%Y %H:%M')) == 13