Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resource bot mixing price #65

Merged
merged 9 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ bots:
max_price_change: 20
expiration_ticks: 5
price_change_coeff: 0.05
dataset_price_weight: 0.5

max_energy_per_player: 0.2

Expand Down
2,332 changes: 0 additions & 2,332 deletions backend/data/df_2331_2009-05-05 08:30:00_2009-08-10 10:30:00.csv

This file was deleted.

2,432 changes: 2,432 additions & 0 deletions backend/data/df_2431_2011-11-06 03:30:00_2012-02-15 09:30:00.csv

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions backend/db/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def fill_tables():
dataset_id=dataset_id,
start_time=datetime.now(),
total_ticks=2300,
tick_time=3000)
tick_time=5000)
nat_game_id = await Game.create(
game_name="Natjecanje",
is_contest=True,
Expand Down Expand Up @@ -160,7 +160,7 @@ async def run_migrations():
volume INT,
PRIMARY KEY (game_id, tick, resource)
)''')

await database.execute('CREATE INDEX CONCURRENTLY tick_idx ON market (tick);')

await database.execute('''
Expand All @@ -174,6 +174,11 @@ async def run_migrations():
biomass INT NOT NULL,
gas INT NOT NULL,
oil INT NOT NULL,
coal_price INT NOT NULL,
uranium_price INT NOT NULL,
biomass_price INT NOT NULL,
gas_price INT NOT NULL,
oil_price INT NOT NULL,
geothermal INT NOT NULL,
wind INT NOT NULL,
solar INT NOT NULL,
Expand Down Expand Up @@ -230,7 +235,12 @@ async def run_migrations():
solar=row["SOLAR"],
hydro=row["HYDRO"],
energy_demand=row["ENERGY_DEMAND"],
max_energy_price=row["MAX_ENERGY_PRICE"]
max_energy_price=row["MAX_ENERGY_PRICE"],
coal_price=row["COAL_PRICE"],
uranium_price=row["URANIUM_PRICE"],
biomass_price=row["BIOMASS_PRICE"],
gas_price=row["GAS_PRICE"],
oil_price=row["OIL_PRICE"]
)
i += 1
logger.info(f"Added dataset {x}")
Expand Down
12 changes: 12 additions & 0 deletions backend/game/bots/resource_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
price_change_coeff = config['bots']['price_change_coeff']
max_price_change = config['bots']['max_price_change']
expiration_ticks = config['bots']['expiration_ticks']
dataset_price_weight = config['bots']['dataset_price_weight']


class ResourceBot(Bot):
Expand Down Expand Up @@ -49,6 +50,9 @@ async def run(self, tick_data: TickData):
orders = await self.get_last_orders()

for resource in Resource:
if resource == Resource.energy:
continue

resource_orders = orders[resource]
resource_sum = resources_sum[resource]
buy_price = self.buy_prices[resource]
Expand Down Expand Up @@ -79,11 +83,19 @@ async def run(self, tick_data: TickData):
if buy_price == sell_price:
buy_price = sell_price - 1

buy_price = self.mix_dataset_price(
tick_data.dataset_row, buy_price, resource)
sell_price = self.mix_dataset_price(
tick_data.dataset_row, sell_price, resource)

await self.create_orders(tick_data.game.current_tick,
resource, buy_price, sell_price, buy_volume, sell_volume)
self.buy_prices[resource] = buy_price
self.sell_prices[resource] = sell_price

def mix_dataset_price(self, dataset_row, price, resource):
return dataset_price_weight * dataset_row[resource.name.lower() + "_price"] + (1 - dataset_price_weight) * price

def get_filled_perc(self, orders: List[Order]):
size = {side: 0 for side in OrderSide}
filled_size = {side: 0 for side in OrderSide}
Expand Down
12 changes: 11 additions & 1 deletion backend/game/fixtures/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ def dataset_row():
solar=8,
hydro=9,
energy_demand=100,
max_energy_price=1000
max_energy_price=1000,
coal_price=1,
uranium_price=2,
biomass_price=3,
gas_price=4,
oil_price=5,
)


Expand Down Expand Up @@ -96,6 +101,11 @@ def get_tick_data(markets, players, user_cancelled_orders=[], pending_orders=[],
biomass=3,
gas=4,
oil=5,
coal_price=coal,
uranium_price=2,
biomass_price=3,
gas_price=4,
oil_price=5,
geothermal=6,
wind=7,
solar=8,
Expand Down
182 changes: 126 additions & 56 deletions backend/game/tick/test_ticker_run_all_game_ticks.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,152 @@
from collections import defaultdict
from databases import Database
import pytest
from datetime import datetime, timedelta
from unittest.mock import patch
from unittest.mock import AsyncMock, MagicMock, patch
from game.tick import Ticker, GameData
from model import Game
from unittest.mock import Mock


@pytest.mark.asyncio
async def test_run_all_game_ticks_game_just_finished():
# Prepare
game = Game(game_id=1, game_name="Test Game", start_time=datetime.now(), current_tick=10,
total_ticks=10, is_finished=False, dataset_id=1, bots="", tick_time=1000, is_contest=False)
ticker = Ticker()
ticker.game_data[game.game_id] = GameData(game, {})
async def test_run_tick_manager():

games = [Game(game_id=1, game_name="Game1", is_finished=False,
start_time=datetime.now(), current_tick=0, total_ticks=10, dataset_id=1, tick_time=1000, is_contest=True)]
mock_game_list = AsyncMock(return_value=games)

mock_start_game = AsyncMock()
mock_end_game = AsyncMock()

with patch('model.Game.list', new=mock_game_list), \
patch('game.tick.Ticker.start_game', new=mock_start_game), \
patch('game.tick.Ticker.end_game', new=mock_end_game):

ticker = Ticker()

await ticker.run_tick_manager(1)

mock_start_game.assert_called_once_with(games[0])

mock_end_game.assert_not_called()


@pytest.mark.asyncio
async def test_run_tick_manager_game_finished():

games = [Game(game_id=1, game_name="Game1", is_finished=True,
start_time=datetime.now(), current_tick=0, total_ticks=10, dataset_id=1, tick_time=1000, is_contest=True)]
mock_game_list = AsyncMock(return_value=games)

mock_start_game = AsyncMock()
mock_end_game = AsyncMock()

with patch('model.Game.list', new=mock_game_list), \
patch('game.tick.Ticker.start_game', new=mock_start_game), \
patch('game.tick.Ticker.end_game', new=mock_end_game):

ticker = Ticker()

await ticker.run_tick_manager(1)

mock_start_game.assert_not_called()

mock_end_game.assert_not_called()


@pytest.mark.asyncio
async def test_run_tick_manager_game_not_started():

games = [Game(game_id=1, game_name="Game1", is_finished=False,
start_time=datetime.now() + timedelta(seconds=10), current_tick=0, total_ticks=10, dataset_id=1, tick_time=1000, is_contest=True)]
mock_game_list = AsyncMock(return_value=games)

mock_start_game = AsyncMock()
mock_end_game = AsyncMock()

# Execute
with patch.object(Game, 'list') as mock_game_list:
mock_game_list.return_value = [game]
with patch.object(Game, 'update') as mock_game_update:
await ticker.run_all_game_ticks()
with patch('model.Game.list', new=mock_game_list), \
patch('game.tick.Ticker.start_game', new=mock_start_game), \
patch('game.tick.Ticker.end_game', new=mock_end_game):

# Verify
assert mock_game_update.call_count == 1
mock_game_update.assert_called_with(game_id=game.game_id, is_finished=True)
ticker = Ticker()

await ticker.run_tick_manager(1)

mock_start_game.assert_not_called()

mock_end_game.assert_not_called()


@pytest.mark.asyncio
async def test_run_all_game_ticks_game_not_started():
# Prepare
game = Game(game_id=2, game_name="Test Game 2", start_time=datetime.now() + timedelta(hours=1),
current_tick=1, total_ticks=10, is_finished=False, dataset_id=1, bots="", tick_time=1000, is_contest=False)
async def test_end_game():
game = Game(game_id=1, game_name="Game1", is_finished=False,
start_time=datetime.now() - timedelta(seconds=1), current_tick=10, total_ticks=10, dataset_id=1, tick_time=1000, is_contest=True)

ticker = Ticker()
ticker.game_data[game.game_id] = GameData(game, {})

# Execute
with patch.object(Game, 'list') as mock_game_list:
mock_game_list.return_value = [game]
await ticker.run_all_game_ticks()
ticker.game_data[1] = Mock()
ticker.game_futures[1] = Mock()

mock_game_update = AsyncMock()

with patch('model.Game.update', new=mock_game_update):

# Verify
# Tick should not increase since the game has not started yet
assert game.current_tick == 1
await ticker.end_game(game)

mock_game_update.assert_called_once_with(
game_id=1, is_finished=True)

assert 1 not in ticker.game_data

ticker.game_futures[1].cancel.assert_called_once()


@pytest.mark.asyncio
async def test_run_all_game_ticks_game_started():
# Prepare
game = Game(game_id=3, game_name="Test Game 3", start_time=datetime.now() - timedelta(hours=1),
current_tick=1, total_ticks=10, is_finished=False, dataset_id=1, bots="", tick_time=1000, is_contest=False)
async def test_start_game():
game = Game(game_id=1, game_name="Game1", is_finished=False,
start_time=datetime.now() - timedelta(seconds=1), current_tick=10, total_ticks=10, dataset_id=1, tick_time=1000, is_contest=True)

ticker = Ticker()
ticker.game_data[game.game_id] = GameData(game, {})

with patch.object(Database, 'transaction') as mock_transaction:
with patch.object(Database, 'execute') as mock_execute:
with patch.object(Game, 'list') as mock_game_list:
mock_game_list.return_value = [game]
with patch.object(Ticker, 'run_game_tick') as mock_run_game_tick:
await ticker.run_all_game_ticks()
ticker.game_data[1] = Mock()
ticker.game_futures[1] = Mock()

mock_game_update = AsyncMock()

with patch('model.Game.update', new=mock_game_update) as mock_game_update, \
patch('game.tick.Ticker.delete_all_running_bots') as mock_delete_all_running_bots, \
patch('game.tick.Ticker.run_game') as mock_run_game:

mock_run_game_tick.assert_called_once_with(game)
await ticker.start_game(game)

mock_delete_all_running_bots.assert_called_once_with(1)

assert 1 in ticker.game_data
assert 1 in ticker.game_futures

await ticker.game_futures[1]


@pytest.mark.asyncio
async def test_run_all_game_ticks_game_finished():
# Prepare
game = Game(game_id=5, game_name="Test Game 5", start_time=datetime.now() - timedelta(hours=1),
current_tick=10, total_ticks=10, is_finished=True, dataset_id=1, bots="", tick_time=1000, is_contest=False)
async def test_run_game():

game = Game(game_id=1, game_name="Game1", is_finished=False,
start_time=datetime.now() - timedelta(seconds=1), current_tick=9, total_ticks=10, dataset_id=1, tick_time=1000, is_contest=True)

ticker = Ticker()
ticker.game_data[game.game_id] = GameData(game, {})

# Execute
with patch.object(Game, 'list') as mock_game_list:
mock_game_list.return_value = [game]
with patch.object(Ticker, 'run_game_tick') as mock_run_game_tick:
await ticker.run_all_game_ticks()

# Verify
mock_run_game_tick.assert_not_called()
assert game.current_tick == 10
assert game.is_finished is True
assert game.game_id in ticker.game_data

with patch('model.Game.get') as mock_get, \
patch('databases.Database.transaction') as mock_transaction, \
patch('databases.Database.execute') as mock_execute, \
patch('game.tick.Ticker.run_game_tick') as mock_run_game_tick, \
patch('asyncio.sleep') as mock_sleep:

mock_get.return_value = game

ticker.run_game_tick = MagicMock()

await ticker.run_game(game, iters=1)

mock_get.assert_called_once_with(game_id=1)

ticker.run_game_tick.assert_called_once_with(game)
4 changes: 4 additions & 0 deletions backend/game/tick/test_ticker_test_run_game_tick.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from pprint import pprint
import pytest
from model import Game
from game.tick import Ticker
from unittest.mock import patch
from tick.test_tick_fixtures import *

import tracemalloc
tracemalloc.start()


@pytest.mark.asyncio
async def test_run_game_tick(
Expand Down
Loading
Loading