From 1e42a2a221599152c75c028bdbe90b2ab177f823 Mon Sep 17 00:00:00 2001 From: rangoiv Date: Sat, 17 Feb 2024 18:47:05 +0100 Subject: [PATCH 1/3] started working on resource bots --- backend/config.yaml | 7 +++ backend/db/table.py | 4 -- backend/game/bots/__init__.py | 1 + backend/game/bots/bot.py | 3 +- backend/game/bots/bots.py | 5 +- backend/game/bots/resource_bot.py | 58 +++++++++++++++++++ backend/game/tick/__init__.py | 3 +- backend/game/tick/test_tick_fixtures.py | 2 +- backend/game/tick/test_ticker_bots.py | 12 ++-- .../game/tick/test_ticker_db_operations.py | 9 ++- .../tick/test_ticker_run_all_game_ticks.py | 4 +- backend/game/tick/test_ticker_run_markets.py | 17 +++--- .../tick/test_ticker_test_run_game_tick.py | 8 +-- backend/game/tick/tick_data.py | 19 ++++++ backend/game/tick/ticker.py | 34 ++++------- backend/model/power_plant_types.py | 9 --- 16 files changed, 129 insertions(+), 66 deletions(-) create mode 100644 backend/game/bots/resource_bot.py create mode 100644 backend/game/tick/tick_data.py diff --git a/backend/config.yaml b/backend/config.yaml index 1efe390..393812f 100644 --- a/backend/config.yaml +++ b/backend/config.yaml @@ -19,6 +19,13 @@ player: bots: team_name: bots team_secret: oiajsdpifjapsndkcapis + resource_sume: 1000 + default_volume: 100 + min_volume: 20 + max_volume: 600 + min_price: 1 + max_price: 100 + expiration_ticks: 5 max_energy_per_player: 0.2 diff --git a/backend/db/table.py b/backend/db/table.py index ee4bf59..cf30200 100644 --- a/backend/db/table.py +++ b/backend/db/table.py @@ -8,10 +8,6 @@ class Table: table_name = None - def get_kwargs(self) -> dict: - cols = [field.name for field in fields(self)] - return {col: self.__getattribute__(col) for col in cols} - @classmethod async def create(cls, col_nums: int = 1, *args, **kwargs) -> int: data = cls(*[0 for _ in range(col_nums)], *args, **kwargs) diff --git a/backend/game/bots/__init__.py b/backend/game/bots/__init__.py index 0af0d00..9a13f56 100644 --- a/backend/game/bots/__init__.py +++ b/backend/game/bots/__init__.py @@ -1,3 +1,4 @@ from .bot import Bot from .bots import Bots from .dummy import DummyBot +from .resource_bot import ResourceBot diff --git a/backend/game/bots/bot.py b/backend/game/bots/bot.py index 2f22398..1abe484 100644 --- a/backend/game/bots/bot.py +++ b/backend/game/bots/bot.py @@ -1,6 +1,4 @@ -from typing import List import abc -from model import Order class Bot(): @@ -10,3 +8,4 @@ def __init__(self, *args, **kwargs): @abc.abstractmethod async def run(self, *args, **kwargs) -> None: pass + diff --git a/backend/game/bots/bots.py b/backend/game/bots/bots.py index 45bd977..8dbf85e 100644 --- a/backend/game/bots/bots.py +++ b/backend/game/bots/bots.py @@ -1,12 +1,15 @@ from fastapi import HTTPException from typing import List, Tuple + from .bot import Bot from .dummy import DummyBot +from .resource_bot import ResourceBot class Bots: bots = { - "dummy": DummyBot + "dummy": DummyBot, + "resources": ResourceBot } def exists(bot_id): diff --git a/backend/game/bots/resource_bot.py b/backend/game/bots/resource_bot.py new file mode 100644 index 0000000..83acc90 --- /dev/null +++ b/backend/game/bots/resource_bot.py @@ -0,0 +1,58 @@ +from typing import List +import abc +from game.tick.tick_data import TickData +from model import Order, Resource +from . import Bot +from config import config + + +class ResourceBot(Bot): + def __init__(self, *args, **kwargs): + self.prices = {resource.name: 50 for resource in Resource} + self.order_ids = [] + self.last_tick = -999 + + + async def run(self, tick_data: TickData): + expiration_ticks = config['bots']['expiration_ticks'] + if tick_data.game.current_tick < self.last_tick + expiration_ticks: + return + self.last_tick = tick_data.game.current_tick + + resources_sum = {resource.name: 0 for resource in Resource} + for player in tick_data.players.values(): + for resource in Resource: + resources_sum[resource.name] += player[resource.name] + + resource_wanted_sum = config['bots']['resource_sum'] + default_volume = config['bots']['default_volume'] + min_volume = config['bots']['min_volume'] + max_volume = config['bots']['max_volume'] + min_price = config['bots']['min_price'] + max_price = config['bots']['max_price'] + + for resource in Resource: + resource_sum = resources_sum[resource.name] + price = self.prices[resource.name] + + # pozitivno ako bot treba otkupiti vise nego prodati + wanted_diff = resource_wanted_sum - resource_sum + + # koliko bot kupuje s trzista + buy_volume = default_volume + wanted_diff + # koliko bot stavlja na trziste + sell_volume = wanted_diff - wanted_diff + + buy_volume = clamp(min_volume, max_volume, buy_volume) + sell_volume = clamp(min_volume, max_volume, sell_volume) + + price = clamp(min_price, max_price, price) + + self.price = price + + def get_last_orders(self, tick: int): + Order.list(player_id=self.player_id, tick=tick) + + +def clamp(_min, _max, x): + return max(_min, min(_max, x)) \ No newline at end of file diff --git a/backend/game/tick/__init__.py b/backend/game/tick/__init__.py index ee0a30c..11d4f11 100644 --- a/backend/game/tick/__init__.py +++ b/backend/game/tick/__init__.py @@ -1 +1,2 @@ -from .ticker import Ticker, TickData, GameData +from .ticker import Ticker, GameData +from .tick_data import TickData \ No newline at end of file diff --git a/backend/game/tick/test_tick_fixtures.py b/backend/game/tick/test_tick_fixtures.py index d1ac880..e78ab81 100644 --- a/backend/game/tick/test_tick_fixtures.py +++ b/backend/game/tick/test_tick_fixtures.py @@ -1,6 +1,6 @@ from datetime import datetime import pytest -from game.tick.ticker import TickData, Ticker, GameData +from game.tick import TickData, Ticker, GameData from model import Game, Player, PowerPlant, Order, OrderStatus, Resource diff --git a/backend/game/tick/test_ticker_bots.py b/backend/game/tick/test_ticker_bots.py index 86c66e4..82e4d78 100644 --- a/backend/game/tick/test_ticker_bots.py +++ b/backend/game/tick/test_ticker_bots.py @@ -1,13 +1,14 @@ import pytest from unittest.mock import MagicMock, patch from datetime import datetime -from game.tick.ticker import Ticker, GameData +from game.tick import Ticker, GameData from model import Game -from game.bots import DummyBot +from game.bots import DummyBot, ResourceBot +from game.fixtures.fixtures import * @pytest.mark.asyncio -async def test_run_bots(): +async def test_run_bots(get_tick_data): # Create sample game game = Game( game_id=1, @@ -36,11 +37,12 @@ async def test_run_bots(): # Set the bots for the game ticker.game_data[game.game_id] = GameData(game, players) + tick_data = get_tick_data(power_plants={}, markets={}, players={}) # Run the method being tested - await ticker.run_bots(game) + await ticker.run_bots(tick_data) # Assertions # Ensure Bot.run is called once for each bot assert mock_run.call_count == len(bots) - mock_run.assert_called_with() # Ensure Bot.run is called with no arguments + mock_run.assert_called_with(tick_data) # Ensure Bot.run is called with no arguments diff --git a/backend/game/tick/test_ticker_db_operations.py b/backend/game/tick/test_ticker_db_operations.py index a93b991..7279242 100644 --- a/backend/game/tick/test_ticker_db_operations.py +++ b/backend/game/tick/test_ticker_db_operations.py @@ -1,11 +1,10 @@ import pandas as pd import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from datetime import datetime -from game.tick.ticker import GameData -from model import Player, PowerPlant, Game, Order, OrderStatus, Resource +from model import Order, OrderStatus, Resource from model.order_types import OrderSide, OrderType -from tick import Ticker, TickData +from game.tick import Ticker, TickData from tick.test_tick_fixtures import * @@ -58,7 +57,7 @@ async def test_save_tick_data(mock_order_update, mock_powerplant_update, mock_pl updated_orders=sample_update_orders ) - await ticker.save_tick_data(sample_game, tick_data) + await ticker.save_tick_data(tick_data) assert mock_player_update.call_count == len(sample_players) assert mock_powerplant_update.call_count == len(sample_power_plants[1]) + len( diff --git a/backend/game/tick/test_ticker_run_all_game_ticks.py b/backend/game/tick/test_ticker_run_all_game_ticks.py index 7ebb163..bf08164 100644 --- a/backend/game/tick/test_ticker_run_all_game_ticks.py +++ b/backend/game/tick/test_ticker_run_all_game_ticks.py @@ -1,7 +1,7 @@ import pytest from datetime import datetime, timedelta -from unittest.mock import MagicMock, patch -from game.tick.ticker import Ticker, GameData +from unittest.mock import patch +from game.tick import Ticker, GameData from model import Game diff --git a/backend/game/tick/test_ticker_run_markets.py b/backend/game/tick/test_ticker_run_markets.py index fa129ed..578f042 100644 --- a/backend/game/tick/test_ticker_run_markets.py +++ b/backend/game/tick/test_ticker_run_markets.py @@ -37,7 +37,7 @@ def test_run_markets_no_match(get_tick_data, get_order, get_ticker, get_player, } ) - tick_data = ticker.run_markets(tick_data, 1) + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order1.order_id] == fresh_order1 assert tick_data.updated_orders[order2.order_id] == fresh_order2 @@ -71,7 +71,7 @@ def test_run_markets_match(get_tick_data, get_order, get_ticker, get_player, get } ) - tick_data = ticker.run_markets(tick_data, 1) + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order1.order_id].order_status == OrderStatus.ACTIVE assert tick_data.updated_orders[order2.order_id].order_status == OrderStatus.COMPLETED @@ -114,7 +114,7 @@ def test_run_markets_match_insufficient_funds(get_tick_data, get_order, get_tick } ) - tick_data = ticker.run_markets(tick_data, 1) + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order1.order_id].order_status == OrderStatus.CANCELLED assert tick_data.updated_orders[order2.order_id].order_status == OrderStatus.ACTIVE @@ -157,7 +157,7 @@ def test_run_markets_match_insufficient_resources(get_tick_data, get_order, get_ } ) - tick_data = ticker.run_markets(tick_data, 1) + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order1.order_id].order_status == OrderStatus.ACTIVE assert tick_data.updated_orders[order2.order_id].order_status == OrderStatus.CANCELLED @@ -202,25 +202,28 @@ def test_run_markets_cancel(get_tick_data, get_order, get_ticker, get_player, ge tick_data.pending_orders = [order1] - tick_data = ticker.run_markets(tick_data, 1) + tick_data.game.current_tick = 1 + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order1.order_id].order_status == OrderStatus.ACTIVE assert len(tick_data.updated_orders) == 1 + tick_data.game.current_tick = 2 tick_data.pending_orders = [] tick_data.updated_orders = {} tick_data.user_cancelled_orders = [order1_cancelled] - tick_data = ticker.run_markets(tick_data, 2) + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order1.order_id].order_status == OrderStatus.CANCELLED assert len(tick_data.updated_orders) == 1 + tick_data.game.current_tick = 3 tick_data.pending_orders = [order2] tick_data.updated_orders = {} tick_data.user_cancelled_orders = [] - tick_data = ticker.run_markets(tick_data, 3) + tick_data = ticker.run_markets(tick_data) assert tick_data.updated_orders[order2.order_id].order_status == OrderStatus.ACTIVE assert len(tick_data.updated_orders) == 1 diff --git a/backend/game/tick/test_ticker_test_run_game_tick.py b/backend/game/tick/test_ticker_test_run_game_tick.py index 55dbcd2..48223e7 100644 --- a/backend/game/tick/test_ticker_test_run_game_tick.py +++ b/backend/game/tick/test_ticker_test_run_game_tick.py @@ -1,9 +1,7 @@ import pytest -from datetime import datetime -from model import Game, Player -from game.tick.ticker import Ticker, TickData +from model import Game +from game.tick import Ticker from unittest.mock import patch -from model.power_plant import PowerPlant from tick.test_tick_fixtures import * @@ -37,4 +35,4 @@ async def test_run_game_tick( Ticker.save_tick_data.assert_called_once() Game.update.assert_called_once_with( game_id=sample_game.game_id, current_tick=sample_game.current_tick + 1) - Ticker.run_bots.assert_called_once_with(sample_game) + Ticker.run_bots.assert_called_once_with(tick_data) diff --git a/backend/game/tick/tick_data.py b/backend/game/tick/tick_data.py new file mode 100644 index 0000000..3c2b07c --- /dev/null +++ b/backend/game/tick/tick_data.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass, field +from model import Player, PowerPlant, Game, Order +from game.market import ResourceMarket +from game.bots.bot import Bot + + +@dataclass +class TickData: + game: Game + players: dict[int, Player] + power_plants: dict[int, list[PowerPlant]] + markets: dict[int, ResourceMarket] + bots: list[Bot] + + dataset_row: dict = field(default_factory=dict) + + pending_orders: list[Order] = field(default_factory=list) + user_cancelled_orders: list[Order] = field(default_factory=list) + updated_orders: dict[int, Order] = field(default_factory=dict) \ No newline at end of file diff --git a/backend/game/tick/ticker.py b/backend/game/tick/ticker.py index 4357ee5..fd6f252 100644 --- a/backend/game/tick/ticker.py +++ b/backend/game/tick/ticker.py @@ -9,6 +9,7 @@ from game.market import ResourceMarket, EnergyMarket from game.bots import Bots, Bot from config import config +from .tick_data import TickData class GameData: @@ -23,21 +24,6 @@ def __init__(self, game: Game, players: dict[int, Player]): self.bots = Bots.create_bots(game.bots) -@dataclass -class TickData: - game: Game - players: dict[int, Player] - power_plants: dict[int, list[PowerPlant]] - markets: dict[int, ResourceMarket] - bots: list[Bot] - - dataset_row: dict = field(default_factory=dict) - - pending_orders: list[Order] = field(default_factory=list) - user_cancelled_orders: list[Order] = field(default_factory=list) - updated_orders: dict[int, Order] = field(default_factory=dict) - - class Ticker: # in ram data @@ -74,17 +60,17 @@ async def run_all_game_ticks(self): async def run_game_tick(self, game: Game): tick_data = await self.get_tick_data(game) - tick_data = self.run_markets(tick_data, game.current_tick) + tick_data = self.run_markets(tick_data) tick_data = self.run_power_plants(tick_data) tick_data, energy_sold = self.run_electricity_market( tick_data, self.game_data[game.game_id].energy_market) await self.save_electricity_orders( game, tick_data.players, energy_sold, game.current_tick) - await self.save_tick_data(game, tick_data) + await self.save_tick_data(tick_data) await Game.update(game_id=game.game_id, current_tick=game.current_tick + 1) - await self.run_bots(game) + await self.run_bots(tick_data) async def get_tick_data(self, game: Game) -> TickData: players = { @@ -115,7 +101,7 @@ async def get_tick_data(self, game: Game) -> TickData: return tick_data - def run_markets(self, tick_data: TickData, tick: int): + def run_markets(self, tick_data: TickData): updated_orders = {} for order in tick_data.user_cancelled_orders: @@ -129,7 +115,7 @@ def run_markets(self, tick_data: TickData, tick: int): game_data = self.game_data[tick_data.game.game_id] market = game_data.markets[order.resource.value] - updated = market.match(order, tick) + updated = market.match(order, tick_data.game.current_tick) updated_orders.update(updated) tick_data.updated_orders.update(updated_orders) @@ -189,7 +175,7 @@ async def save_electricity_orders(self, players: dict[int, Player], game: Game, resource=Resource.energy.value ) - async def save_tick_data(self, game: Game, tick_data: TickData): + async def save_tick_data(self, tick_data: TickData): for player in tick_data.players.values(): await Player.update(**dataclasses.asdict(player)) @@ -200,8 +186,8 @@ async def save_tick_data(self, game: Game, tick_data: TickData): for order in tick_data.updated_orders.values(): await Order.update(**dataclasses.asdict(order)) - async def run_bots(self, game: Game): - bots = self.game_data[game.game_id].bots + async def run_bots(self, tick_data: TickData): + bots = self.game_data[tick_data.game.game_id].bots for bot in bots: - await bot.run() + await bot.run(tick_data) diff --git a/backend/model/power_plant_types.py b/backend/model/power_plant_types.py index 9d89839..75613d5 100644 --- a/backend/model/power_plant_types.py +++ b/backend/model/power_plant_types.py @@ -40,12 +40,3 @@ def is_renewable(self): def cap(x): return min(1, max(0, x)) - - -for power_plant_type in PowerPlantType: - assert power_plant_type.name.lower( - ) in config["power_plant"]["base_prices"], f"Missing price for {power_plant_type.name}" - assert power_plant_type.name.lower( - ) in config["power_plant"]["warmup_coeff"], f"Missing price for {power_plant_type.name}" - assert power_plant_type.name.lower( - ) in config["power_plant"]["cooldown_coeff"], f"Missing price for {power_plant_type.name}" From 7177ad0fce492be64b9345a3d02d3e8a42a08c0c Mon Sep 17 00:00:00 2001 From: rangoiv Date: Sat, 17 Feb 2024 20:32:48 +0100 Subject: [PATCH 2/3] finished logic --- backend/config.yaml | 4 +- backend/game/bots/bot.py | 1 + backend/game/bots/resource_bot.py | 115 ++++++++++++++++++++++-------- backend/game/tick/ticker.py | 2 + 4 files changed, 92 insertions(+), 30 deletions(-) diff --git a/backend/config.yaml b/backend/config.yaml index 393812f..313515b 100644 --- a/backend/config.yaml +++ b/backend/config.yaml @@ -19,13 +19,15 @@ player: bots: team_name: bots team_secret: oiajsdpifjapsndkcapis - resource_sume: 1000 + resource_sum: 1000 default_volume: 100 min_volume: 20 max_volume: 600 min_price: 1 max_price: 100 + max_price_change: 20 expiration_ticks: 5 + price_change_coeff: 0.05 max_energy_per_player: 0.2 diff --git a/backend/game/bots/bot.py b/backend/game/bots/bot.py index 1abe484..78e6cd3 100644 --- a/backend/game/bots/bot.py +++ b/backend/game/bots/bot.py @@ -4,6 +4,7 @@ class Bot(): def __init__(self, *args, **kwargs): self.player_id = 0 + self.game_id = 0 @abc.abstractmethod async def run(self, *args, **kwargs) -> None: diff --git a/backend/game/bots/resource_bot.py b/backend/game/bots/resource_bot.py index 83acc90..c1f281e 100644 --- a/backend/game/bots/resource_bot.py +++ b/backend/game/bots/resource_bot.py @@ -1,57 +1,114 @@ from typing import List +import dataclasses import abc from game.tick.tick_data import TickData -from model import Order, Resource +from model import Order, Resource, OrderSide from . import Bot from config import config +resource_wanted_sum = config['bots']['resource_sum'] +default_volume = config['bots']['default_volume'] +min_volume = config['bots']['min_volume'] +max_volume = config['bots']['max_volume'] +min_price = config['bots']['min_price'] +max_price = config['bots']['max_price'] +price_change_coeff = config['bots']['price_change_coeff'] +max_price_change = config['bots']['max_price_change'] +expiration_ticks = config['bots']['expiration_ticks'] + + class ResourceBot(Bot): def __init__(self, *args, **kwargs): - self.prices = {resource.name: 50 for resource in Resource} - self.order_ids = [] - self.last_tick = -999 - + self.buy_prices = {resource: 50 for resource in Resource} + self.sell_prices = {resource: 50 for resource in Resource} + self.last_tick = None async def run(self, tick_data: TickData): - expiration_ticks = config['bots']['expiration_ticks'] - if tick_data.game.current_tick < self.last_tick + expiration_ticks: + if self.last_tick is not None and tick_data.game.current_tick < self.last_tick + expiration_ticks: return self.last_tick = tick_data.game.current_tick - resources_sum = {resource.name: 0 for resource in Resource} - for player in tick_data.players.values(): - for resource in Resource: - resources_sum[resource.name] += player[resource.name] - - resource_wanted_sum = config['bots']['resource_sum'] - default_volume = config['bots']['default_volume'] - min_volume = config['bots']['min_volume'] - max_volume = config['bots']['max_volume'] - min_price = config['bots']['min_price'] - max_price = config['bots']['max_price'] + resources_sum = {resource: 0 for resource in Resource} + for resource in Resource: + for player in tick_data.players.values(): + resources_sum[resource] += player[resource] + + orders = await self.get_last_orders() for resource in Resource: - resource_sum = resources_sum[resource.name] - price = self.prices[resource.name] + resource_orders = orders[resource] + resource_sum = resources_sum[resource] + buy_price = self.buy_prices[resource] + sell_price = self.sell_prices[resource] # pozitivno ako bot treba otkupiti vise nego prodati - wanted_diff = resource_wanted_sum - resource_sum - + wanted_volume_change = resource_wanted_sum - resource_sum # koliko bot kupuje s trzista - buy_volume = default_volume + wanted_diff + buy_volume = default_volume + wanted_volume_change # koliko bot stavlja na trziste - sell_volume = wanted_diff - wanted_diff - + sell_volume = wanted_volume_change - wanted_volume_change buy_volume = clamp(min_volume, max_volume, buy_volume) sell_volume = clamp(min_volume, max_volume, sell_volume) - price = clamp(min_price, max_price, price) + filled_buy_perc, filled_sell_perc = self.get_filled_perc(resource_orders) + buy_price -= price_change_coeff * buy_price * (1-2*filled_buy_perc) + sell_price += price_change_coeff * sell_price * (1-2*filled_sell_perc) + + if buy_price >= sell_price: + price = (buy_price * buy_volume + sell_price * sell_volume) / (buy_volume + sell_volume) + buy_price = price + sell_price = price + buy_price = clamp(min_price, max_price, buy_price) + sell_price = clamp(min_price, max_price, sell_price) + if buy_price == sell_price: + buy_price = sell_price - 1 + + 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 - self.price = price + def get_filled_perc(self, orders: list[Order]): + size = {side: 0 for side in OrderSide} + filled_size = {side: 0 for side in OrderSide} + for order in orders: + size[order.order_side] += order.size + filled_size[order.order_side] += order.filled_size + filled_perc = {side: filled_size[side] / size[side] for side in OrderSide} + return filled_perc[OrderSide.BUY], filled_perc[OrderSide.SELL] - def get_last_orders(self, tick: int): - Order.list(player_id=self.player_id, tick=tick) + async def get_last_orders(self) -> dict[str, Order]: + if self.last_tick is None: return [] + orders_list = await Order.list(player_id=self.player_id, tick=self.last_tick) + orders = {resource: [] for resource in Resource} + for order in orders_list: + orders[order.resource].append(order) + return orders + + async def create_orders(self, tick, resource, buy_price, sell_price, buy_volume, sell_volume) -> None: + Order.create(**dataclasses.asdict(Order( + game_id=self.game_id, + player_id=self.player_id, + price=buy_price, + tick=tick, + timestamp=tick, + size=buy_volume, + order_side=OrderSide.BUY, + resource=resource, + expiration_tick=tick+expiration_ticks + ))) + Order.create(**dataclasses.asdict(Order( + game_id=self.game_id, + player_id=self.player_id, + price=sell_price, + tick=tick, + timestamp=tick, + size=sell_volume, + order_side=OrderSide.SELL, + resource=resource, + expiration_tick=tick+expiration_ticks + ))) def clamp(_min, _max, x): diff --git a/backend/game/tick/ticker.py b/backend/game/tick/ticker.py index fd6f252..213264b 100644 --- a/backend/game/tick/ticker.py +++ b/backend/game/tick/ticker.py @@ -22,6 +22,8 @@ def __init__(self, game: Game, players: dict[int, Player]): self.energy_market = EnergyMarket() self.bots = Bots.create_bots(game.bots) + for bot in self.bots: + bot.game_id = game.game_id class Ticker: From 9aae6e8510dd5ce41b5641cec546f2b7c892dc29 Mon Sep 17 00:00:00 2001 From: rangoiv Date: Sat, 17 Feb 2024 21:00:33 +0100 Subject: [PATCH 3/3] fixes --- backend/db/migration.py | 4 +-- backend/game/bots/bots.py | 2 +- backend/game/bots/resource_bot.py | 44 ++++++++++++++++++++----------- backend/game/tick/ticker.py | 5 +--- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/backend/db/migration.py b/backend/db/migration.py index 4dad6e2..5905e92 100644 --- a/backend/db/migration.py +++ b/backend/db/migration.py @@ -15,8 +15,8 @@ async def fill_tables(): # dataset_id = await Datasets.create(dataset_name="Dummy dataset", dataset_description="Opis") datasets = await Datasets.list() - not_nat_game_id = await Game.create(game_name="Stalna igra", is_contest=False, bots="dummy:3", dataset_id=1, start_time=datetime.now(), total_ticks=2400, tick_time=3000) - nat_game_id = await Game.create(game_name="Natjecanje", is_contest=True, bots="dummy:2", dataset_id=1, start_time=datetime.now(), total_ticks=10, tick_time=1000) + not_nat_game_id = await Game.create(game_name="Stalna igra", is_contest=False, bots="dummy:3;resource_bot:1", dataset_id=1, start_time=datetime.now(), total_ticks=2400, tick_time=3000) + nat_game_id = await Game.create(game_name="Natjecanje", is_contest=True, bots="dummy:2;resource_bot:1", dataset_id=1, start_time=datetime.now(), total_ticks=10, tick_time=1000) for game_id in [not_nat_game_id, nat_game_id]: await Player.create(player_name="Goran", is_active=True, is_bot=False, game_id=game_id, team_id=g_team_id, money=15000, coal=1000) diff --git a/backend/game/bots/bots.py b/backend/game/bots/bots.py index 8dbf85e..0110ab0 100644 --- a/backend/game/bots/bots.py +++ b/backend/game/bots/bots.py @@ -9,7 +9,7 @@ class Bots: bots = { "dummy": DummyBot, - "resources": ResourceBot + "resource_bot": ResourceBot } def exists(bot_id): diff --git a/backend/game/bots/resource_bot.py b/backend/game/bots/resource_bot.py index c1f281e..67da7f0 100644 --- a/backend/game/bots/resource_bot.py +++ b/backend/game/bots/resource_bot.py @@ -1,8 +1,6 @@ -from typing import List -import dataclasses -import abc +import pandas as pd from game.tick.tick_data import TickData -from model import Order, Resource, OrderSide +from model import Order, Resource, OrderSide, Team, Player from . import Bot from config import config @@ -20,11 +18,23 @@ class ResourceBot(Bot): def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.buy_prices = {resource: 50 for resource in Resource} self.sell_prices = {resource: 50 for resource in Resource} self.last_tick = None + self.player_id = None async def run(self, tick_data: TickData): + if self.player_id is None: + team = await Team.get(team_secret=config['bots']['team_secret']) + self.player_id = await Player.create( + player_name="resource_bot", + game_id=tick_data.game.game_id, + team_id=team.team_id, + is_bot=True + ) + self.game_id = tick_data.game.game_id + if self.last_tick is not None and tick_data.game.current_tick < self.last_tick + expiration_ticks: return self.last_tick = tick_data.game.current_tick @@ -32,7 +42,7 @@ async def run(self, tick_data: TickData): resources_sum = {resource: 0 for resource in Resource} for resource in Resource: for player in tick_data.players.values(): - resources_sum[resource] += player[resource] + resources_sum[resource] += player[resource.name] orders = await self.get_last_orders() @@ -48,8 +58,8 @@ async def run(self, tick_data: TickData): buy_volume = default_volume + wanted_volume_change # koliko bot stavlja na trziste sell_volume = wanted_volume_change - wanted_volume_change - buy_volume = clamp(min_volume, max_volume, buy_volume) - sell_volume = clamp(min_volume, max_volume, sell_volume) + buy_volume = clamp(min_volume, max_volume, int(buy_volume)) + sell_volume = clamp(min_volume, max_volume, int(sell_volume)) filled_buy_perc, filled_sell_perc = self.get_filled_perc(resource_orders) buy_price -= price_change_coeff * buy_price * (1-2*filled_buy_perc) @@ -59,8 +69,8 @@ async def run(self, tick_data: TickData): price = (buy_price * buy_volume + sell_price * sell_volume) / (buy_volume + sell_volume) buy_price = price sell_price = price - buy_price = clamp(min_price, max_price, buy_price) - sell_price = clamp(min_price, max_price, sell_price) + buy_price = clamp(min_price, max_price, int(buy_price)) + sell_price = clamp(min_price, max_price, int(sell_price)) if buy_price == sell_price: buy_price = sell_price - 1 @@ -75,7 +85,9 @@ def get_filled_perc(self, orders: list[Order]): for order in orders: size[order.order_side] += order.size filled_size[order.order_side] += order.filled_size - filled_perc = {side: filled_size[side] / size[side] for side in OrderSide} + filled_perc = {side: filled_size[side] / size[side] + if size[side] > 0 else 0 + for side in OrderSide} return filled_perc[OrderSide.BUY], filled_perc[OrderSide.SELL] async def get_last_orders(self) -> dict[str, Order]: @@ -87,28 +99,28 @@ async def get_last_orders(self) -> dict[str, Order]: return orders async def create_orders(self, tick, resource, buy_price, sell_price, buy_volume, sell_volume) -> None: - Order.create(**dataclasses.asdict(Order( + await Order.create( game_id=self.game_id, player_id=self.player_id, price=buy_price, tick=tick, - timestamp=tick, + timestamp=pd.Timestamp.now(), size=buy_volume, order_side=OrderSide.BUY, resource=resource, expiration_tick=tick+expiration_ticks - ))) - Order.create(**dataclasses.asdict(Order( + ) + await Order.create( game_id=self.game_id, player_id=self.player_id, price=sell_price, tick=tick, - timestamp=tick, + timestamp=pd.Timestamp.now(), size=sell_volume, order_side=OrderSide.SELL, resource=resource, expiration_tick=tick+expiration_ticks - ))) + ) def clamp(_min, _max, x): diff --git a/backend/game/tick/ticker.py b/backend/game/tick/ticker.py index 213264b..0140e67 100644 --- a/backend/game/tick/ticker.py +++ b/backend/game/tick/ticker.py @@ -22,9 +22,6 @@ def __init__(self, game: Game, players: dict[int, Player]): self.energy_market = EnergyMarket() self.bots = Bots.create_bots(game.bots) - for bot in self.bots: - bot.game_id = game.game_id - class Ticker: @@ -55,7 +52,7 @@ async def run_all_game_ticks(self): continue if self.game_data.get(game.game_id) is None: - self.game_data[game.game_id] = GameData(game, {}) + self.game_data[game.game_id] = GameData(game, {}) await self.run_game_tick(game)