├── .DS_Store ├── market_maker ├── .DS_Store ├── __pycache__ │ ├── ui.cpython-312.pyc │ ├── main.cpython-312.pyc │ ├── __init__.cpython-312.pyc │ └── market_maker.cpython-312.pyc ├── games │ ├── __pycache__ │ │ ├── coin.cpython-312.pyc │ │ ├── dice.cpython-312.pyc │ │ ├── game.cpython-312.pyc │ │ └── poker.cpython-312.pyc │ ├── game.py │ ├── dice.py │ ├── coin.py │ └── poker.py ├── __init__.py ├── main.py ├── market_maker.py └── ui.py ├── README.md └── .cursorrules /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/.DS_Store -------------------------------------------------------------------------------- /market_maker/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/.DS_Store -------------------------------------------------------------------------------- /market_maker/__pycache__/ui.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/__pycache__/ui.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/__pycache__/main.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/__pycache__/main.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/games/__pycache__/coin.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/games/__pycache__/coin.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/games/__pycache__/dice.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/games/__pycache__/dice.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/games/__pycache__/game.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/games/__pycache__/game.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/games/__pycache__/poker.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/games/__pycache__/poker.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/__pycache__/market_maker.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ody-Z/Trading_Game_Simulator/HEAD/market_maker/__pycache__/market_maker.cpython-312.pyc -------------------------------------------------------------------------------- /market_maker/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Market Making Trading Games 3 | A interactive trading platform that simulates market making across different games of chance. 4 | """ 5 | 6 | __version__ = "1.0.0" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Market Making Trading Games 2 | 3 | A interactive trading platform that simulates market making across different games of chance, allowing users to place bets and trade positions with varying odds and conditions. 4 | 5 | ## Games Description 6 | 7 | ### 1. Dice Game 8 | - 3 dices are rolled 9 | - There are different outcomes with different odds that you can bet you money on 10 | 11 | ### 2. Poker Game 12 | - 3 cards are drawn with replacement 13 | - There are different outcomes with different odds that you can bet your money on 14 | - Market Making: The game provides bid/ask prices for the sum of the 3 cards 15 | - You can buy (bet the sum will be higher) 16 | - You can sell (bet the sum will be lower) 17 | - Prices update after each trade 18 | 19 | ### 3. Coin Flip 20 | - 3 coins are flipped at the same time 21 | - Bet on patterns, different outcomes have different odds. 22 | - Streak betting options (consecutive heads/tails): All heads or 2 consecutive heads 23 | - Multiple coin flip combinations: Alternating (HTH or THT), 2 heads, or 2 tails 24 | 25 | 26 | ## Features 27 | - Real-time odds updating after each trade 28 | - Real-time bid and ask updating after each trade (for cards only) 29 | - Virtual currency system: you can set the money of your initial account, PNL is calculated each round. 30 | 31 | 32 | ## Getting Started 33 | 1. Install required dependencies 34 | 2. CD to the project directory 35 | 3. Run **python -m market_maker.main** to start the application 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.cursorrules: -------------------------------------------------------------------------------- 1 | # Cursor Rule File 2 | # ---------------- 3 | # This configuration outlines the general rules to be followed for code generation. 4 | # Image that you are a professional software engineer. 5 | # 1. Code Conciseness & DRY Principle: 6 | # - Ensure that all code is concise and avoids unnecessary repetition. 7 | # - Reuse existing functions and components instead of duplicating logic. 8 | # 9 | # 2. Style and Formatting: 10 | # - Adhere to PEP8 guidelines for Python code formatting. 11 | # - Use clear, descriptive naming conventions and maintain consistent indentation. 12 | # 13 | # 3. Relevance and Minimalism: 14 | # - Generate only what is requested by the user. 15 | # - Do not include extra features, comments, or documentation unless explicitly asked. 16 | # 17 | # 4. Code Quality: 18 | # - Write clean, well-structured, and easily maintainable code. 19 | # - Include error handling and validation only when necessary or requested. 20 | # 21 | # 5. Modularity: 22 | # - Structure code in a modular fashion to facilitate easy integration and testing. 23 | # 24 | # 6. Testing: 25 | # - Generate tests only if the user specifically requests them. 26 | # 27 | # 7. Security: 28 | # - Follow best practices for security and ensure that generated code does not expose vulnerabilities. 29 | # 30 | # 8. Documentation: 31 | # - Include inline comments only when they add value or clarity. 32 | # - Do not generate extensive documentation beyond what is asked. 33 | # 34 | # 9. Respect for User Request: 35 | # - Follow the user's instructions precisely. 36 | # - Avoid generating any additional content or features beyond the scope of the request. 37 | # 38 | # End of Cursor Rule File 39 | -------------------------------------------------------------------------------- /market_maker/games/game.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Dict, List, Any 3 | 4 | class Game(ABC): 5 | def __init__(self): 6 | self.current_odds: Dict[str, float] = {} 7 | self.min_bet: float = 1.0 8 | self.max_bet: float = 1000.0 9 | self.player_balance: float = 0 10 | self.active_bets: Dict[str, float] = {} # Track bets for each outcome 11 | 12 | @abstractmethod 13 | def initialize_game(self) -> None: 14 | """Initialize game specific parameters""" 15 | pass 16 | 17 | @abstractmethod 18 | def calculate_odds(self) -> Dict[str, float]: 19 | """Calculate odds for all possible outcomes""" 20 | pass 21 | 22 | @abstractmethod 23 | def play_round(self) -> Dict[str, Any]: 24 | """Play one round of the game and return results""" 25 | pass 26 | 27 | def place_bet(self, outcome: str, amount: float) -> bool: 28 | """ 29 | Place a bet on a specific outcome 30 | Returns True if bet is valid and placed successfully 31 | """ 32 | if amount < self.min_bet or amount > self.max_bet: 33 | return False 34 | if amount > self.player_balance: 35 | return False 36 | if outcome not in self.current_odds: 37 | return False 38 | 39 | self.player_balance -= amount 40 | self.active_bets[outcome] = amount 41 | return True 42 | 43 | def set_balance(self, balance: float) -> None: 44 | """Set player's initial balance""" 45 | self.player_balance = balance 46 | 47 | def clear_bets(self) -> None: 48 | """Clear all active bets after a round""" 49 | self.active_bets.clear() -------------------------------------------------------------------------------- /market_maker/main.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import sys 3 | import os 4 | from pathlib import Path 5 | from typing import Optional 6 | from .ui import GameUI 7 | 8 | class MarketMakingApp: 9 | def __init__(self): 10 | self.root: Optional[tk.Tk] = None 11 | self.ui: Optional[GameUI] = None 12 | 13 | def setup_environment(self) -> None: 14 | """Setup necessary environment variables and paths""" 15 | # Add project root to Python path 16 | project_root = Path(__file__).parent 17 | sys.path.append(str(project_root)) 18 | 19 | # Create necessary directories if they don't exist 20 | log_dir = project_root / "logs" 21 | log_dir.mkdir(exist_ok=True) 22 | 23 | def initialize(self) -> None: 24 | """Initialize the application""" 25 | try: 26 | # Setup Tkinter root 27 | self.root = tk.Tk() 28 | 29 | # Set window title and icon 30 | self.root.title("Market Making Trading Games") 31 | 32 | # Configure window properties 33 | self.root.minsize(800, 600) 34 | 35 | # Configure grid weights 36 | self.root.grid_rowconfigure(0, weight=1) 37 | self.root.grid_columnconfigure(0, weight=1) 38 | 39 | # Initialize UI 40 | self.ui = GameUI(self.root) 41 | 42 | except Exception as e: 43 | print(f"Error initializing application: {e}") 44 | sys.exit(1) 45 | 46 | def run(self) -> None: 47 | """Run the application""" 48 | try: 49 | # Setup environment 50 | self.setup_environment() 51 | 52 | # Initialize application 53 | self.initialize() 54 | 55 | # Start the main event loop 56 | if self.root: 57 | self.root.mainloop() 58 | 59 | except KeyboardInterrupt: 60 | print("\nApplication terminated by user") 61 | sys.exit(0) 62 | except Exception as e: 63 | print(f"Fatal error: {e}") 64 | sys.exit(1) 65 | finally: 66 | # Cleanup code here if needed 67 | pass 68 | 69 | def main(): 70 | """Main entry point for the application""" 71 | try: 72 | # Create and run application 73 | app = MarketMakingApp() 74 | app.run() 75 | 76 | except Exception as e: 77 | print(f"Failed to start application: {e}") 78 | sys.exit(1) 79 | 80 | if __name__ == "__main__": 81 | main() -------------------------------------------------------------------------------- /market_maker/market_maker.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import random 3 | 4 | class MarketMaker: 5 | def __init__(self): 6 | self.current_bid = 19.0 # Starting bid price 7 | self.current_ask = 20.0 # Starting ask price 8 | self.spread = 1.0 # Minimum spread 9 | self.volatility = 0.3 # Price volatility 10 | self.position = 0 # Net position (positive = long, negative = short) 11 | self.max_position = float('inf') # Increased maximum position size 12 | self.inventory_impact = 0.1 # How much position affects prices 13 | 14 | def get_prices(self) -> Tuple[float, float]: 15 | """Get current bid and ask prices""" 16 | return self.current_bid, self.current_ask 17 | 18 | def update_prices(self, card_sum: int) -> Tuple[float, float]: 19 | """Update prices based on market conditions with random fluctuation around 21""" 20 | # Use fixed base price of 21 instead of card sum 21 | base_price = 21.0 22 | 23 | # Add random market movement (30% range means ±15% from base) 24 | fluctuation_range = base_price * self.volatility # 30% of base price 25 | market_move = random.uniform(-fluctuation_range, fluctuation_range) 26 | base_price += market_move 27 | 28 | # Adjust for inventory position - when long (positive position), raise ask and lower bid 29 | # This encourages balancing the book by making selling more attractive and buying less attractive 30 | inventory_adjustment = self.position * self.inventory_impact 31 | 32 | # Update bid and ask while maintaining minimum spread 33 | self.current_bid = base_price - self.spread/2 - inventory_adjustment 34 | self.current_ask = base_price + self.spread/2 + inventory_adjustment 35 | 36 | # Ensure bid and ask are positive 37 | self.current_bid = max(0.1, self.current_bid) 38 | self.current_ask = max(self.current_bid + self.spread, self.current_ask) 39 | 40 | return self.current_bid, self.current_ask 41 | 42 | def place_trade(self, amount: float, is_buy: bool) -> bool: 43 | """Process a trade request""" 44 | # Check if trade would exceed max position 45 | if is_buy and self.position + amount > self.max_position: 46 | return False 47 | if not is_buy and self.position - amount < -self.max_position: 48 | return False 49 | 50 | # Update position 51 | if is_buy: 52 | self.position += amount 53 | else: 54 | self.position -= amount 55 | 56 | return True 57 | 58 | def get_position(self) -> float: 59 | """Get current position size""" 60 | return self.position -------------------------------------------------------------------------------- /market_maker/games/dice.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | import random 3 | from .game import Game 4 | 5 | class DiceGame(Game): 6 | def __init__(self): 7 | super().__init__() 8 | self.num_dice = 3 9 | self.outcomes = { 10 | "sum_3": "Sum of 3 dice is 3", 11 | "sum_5_10": "Sum of 3 dice is 5 or 10", 12 | "sum_18": "Sum of 3 dice is 18" 13 | } 14 | self.initialize_game() 15 | 16 | def initialize_game(self) -> None: 17 | self.current_odds = self.calculate_odds() 18 | self.clear_bets() 19 | 20 | def calculate_odds(self) -> Dict[str, float]: 21 | base_house_edge = 0.05 22 | 23 | # Add randomness to house edge for each outcome 24 | house_edges = { 25 | "sum_3": base_house_edge + random.uniform(-0.02, 0.05), 26 | "sum_5_10": base_house_edge + random.uniform(-0.01, 0.04), 27 | "sum_18": base_house_edge + random.uniform(-0.02, 0.05), 28 | } 29 | 30 | # Probability calculations 31 | prob_sum_3 = 1/216 # (1,1,1) 32 | prob_sum_5_10 = 25/216 # Combined probability for 5 and 10 33 | prob_sum_18 = 1/216 # (6,6,6) 34 | 35 | # Add market fluctuation 36 | market_fluctuation = 0.10 # 10% maximum fluctuation 37 | 38 | # Convert probabilities to odds and add house edge 39 | odds = { 40 | "sum_3": (1 / prob_sum_3) * (1 + house_edges["sum_3"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 41 | "sum_5_10": (1 / prob_sum_5_10) * (1 + house_edges["sum_5_10"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 42 | "sum_18": (1 / prob_sum_18) * (1 + house_edges["sum_18"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)) 43 | } 44 | 45 | return odds 46 | 47 | def play_round(self) -> Dict[str, Any]: 48 | dice_rolls = [random.randint(1, 6) for _ in range(self.num_dice)] 49 | total = sum(dice_rolls) 50 | 51 | # Determine outcomes 52 | outcomes = { 53 | "sum_3": total == 3, 54 | "sum_5_10": total in [5, 10], 55 | "sum_18": total == 18 56 | } 57 | 58 | # Process winnings 59 | for outcome, won in outcomes.items(): 60 | if outcome in self.active_bets: 61 | bet_amount = self.active_bets[outcome] 62 | if won: 63 | # Return original bet plus winnings 64 | self.player_balance += bet_amount + (bet_amount * (self.current_odds[outcome] - 1)) 65 | # If bet lost, money is already deducted when bet was placed 66 | 67 | results = { 68 | "dice_rolls": dice_rolls, 69 | "total": total, 70 | "outcomes": outcomes 71 | } 72 | 73 | # Clear bets after round 74 | self.clear_bets() 75 | 76 | # Recalculate odds for next round 77 | self.current_odds = self.calculate_odds() 78 | 79 | return results -------------------------------------------------------------------------------- /market_maker/games/coin.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | import random 3 | from .game import Game 4 | 5 | class CoinGame(Game): 6 | def __init__(self): 7 | super().__init__() 8 | self.num_coins = 3 9 | self.outcomes = { 10 | "all_heads": "All heads", 11 | "two_consecutive_heads": "2 consecutive heads", 12 | "alternating": "Alternating (HTH or THT)", 13 | "two_heads": "Exactly 2 heads", 14 | "two_tails": "Exactly 2 tails" 15 | } 16 | self.initialize_game() 17 | 18 | def initialize_game(self) -> None: 19 | self.current_odds = self.calculate_odds() 20 | self.clear_bets() 21 | 22 | def calculate_odds(self) -> Dict[str, float]: 23 | base_house_edge = 0.05 24 | 25 | # Add randomness to house edge for each outcome 26 | # This creates slightly different odds each time 27 | house_edges = { 28 | "all_heads": base_house_edge + random.uniform(-0.02, 0.05), 29 | "two_consecutive_heads": base_house_edge + random.uniform(-0.01, 0.04), 30 | "alternating": base_house_edge + random.uniform(-0.015, 0.045), 31 | "two_heads": base_house_edge + random.uniform(-0.01, 0.03), 32 | "two_tails": base_house_edge + random.uniform(-0.01, 0.03) 33 | } 34 | 35 | # Calculate probabilities 36 | prob_all_heads = 1/8 37 | prob_two_consecutive = 3/8 38 | prob_alternating = 2/8 39 | prob_two_heads = 3/8 40 | prob_two_tails = 3/8 41 | 42 | # Add a small random factor to each probability to simulate market fluctuation 43 | market_fluctuation = 0.1 # 10% maximum fluctuation 44 | 45 | odds = { 46 | "all_heads": (1 / prob_all_heads) * (1 + house_edges["all_heads"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 47 | "two_consecutive_heads": (1 / prob_two_consecutive) * (1 + house_edges["two_consecutive_heads"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 48 | "alternating": (1 / prob_alternating) * (1 + house_edges["alternating"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 49 | "two_heads": (1 / prob_two_heads) * (1 + house_edges["two_heads"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 50 | "two_tails": (1 / prob_two_tails) * (1 + house_edges["two_tails"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)) 51 | } 52 | 53 | return odds 54 | 55 | def play_round(self) -> Dict[str, Any]: 56 | flips = ['H' if random.random() > 0.5 else 'T' for _ in range(self.num_coins)] 57 | 58 | # Check all outcomes 59 | outcomes = { 60 | "all_heads": all(flip == 'H' for flip in flips), 61 | "two_consecutive_heads": any(flips[i:i+2] == ['H', 'H'] for i in range(len(flips)-1)), 62 | "alternating": ''.join(flips) in ['HTH', 'THT'], 63 | "two_heads": sum(flip == 'H' for flip in flips) == 2, 64 | "two_tails": sum(flip == 'T' for flip in flips) == 2 65 | } 66 | 67 | # Process winnings 68 | for outcome, won in outcomes.items(): 69 | if outcome in self.active_bets and won: 70 | bet_amount = self.active_bets[outcome] 71 | self.player_balance += bet_amount * self.current_odds[outcome] 72 | 73 | results = { 74 | "flips": flips, 75 | "outcomes": outcomes 76 | } 77 | 78 | # Clear bets after round 79 | self.clear_bets() 80 | 81 | # Recalculate odds for next round 82 | self.current_odds = self.calculate_odds() 83 | 84 | return results -------------------------------------------------------------------------------- /market_maker/games/poker.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, List, Tuple 2 | import random 3 | from .game import Game 4 | from ..market_maker import MarketMaker 5 | 6 | class PokerGame(Game): 7 | def __init__(self): 8 | super().__init__() 9 | self.deck = self._create_deck() 10 | self.num_cards = 3 11 | self.market_maker = MarketMaker() 12 | self.outcomes = { 13 | "sum_under_10": "Sum of cards under 10", 14 | "sum_10_20": "Sum of cards between 10-20", 15 | "sum_over_20": "Sum of cards over 20", 16 | "all_same_suit": "All cards same suit", 17 | "all_face_cards": "All face cards" 18 | } 19 | # Track market maker trades 20 | self.mm_trades: List[Tuple[float, bool, float]] = [] # [(amount, is_buy, initial_price), ...] 21 | self.initialize_game() 22 | 23 | def initialize_game(self) -> None: 24 | self.current_odds = self.calculate_odds() 25 | self.clear_bets() 26 | self._shuffle_deck() 27 | # Reset market maker trades 28 | self.mm_trades.clear() 29 | 30 | def _create_deck(self) -> List[Tuple[str, str]]: 31 | suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades'] 32 | ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] 33 | return [(rank, suit) for suit in suits for rank in ranks] 34 | 35 | def _shuffle_deck(self) -> None: 36 | random.shuffle(self.deck) 37 | 38 | def _card_value(self, card: Tuple[str, str]) -> int: 39 | rank = card[0] 40 | if rank in ['J', 'Q', 'K']: 41 | return 10 42 | elif rank == 'A': 43 | return 11 44 | return int(rank) 45 | 46 | def calculate_odds(self) -> Dict[str, float]: 47 | base_house_edge = 0.05 48 | 49 | # Add randomness to house edge for each outcome 50 | house_edges = { 51 | "sum_under_10": base_house_edge + random.uniform(-0.02, 0.04), 52 | "sum_10_20": base_house_edge + random.uniform(-0.015, 0.035), 53 | "sum_over_20": base_house_edge + random.uniform(-0.02, 0.04), 54 | "all_same_suit": base_house_edge + random.uniform(-0.01, 0.06), 55 | "all_face_cards": base_house_edge + random.uniform(-0.01, 0.06) 56 | } 57 | 58 | # Calculate probabilities 59 | # These are approximate probabilities 60 | prob_under_10 = 0.3 61 | prob_10_20 = 0.4 62 | prob_over_20 = 0.3 63 | prob_same_suit = 0.05 64 | prob_all_face = 0.037 # (12 face cards / 52 cards)^3 65 | 66 | # Add market fluctuation 67 | market_fluctuation = 0.10 # 10% maximum fluctuation for poker (more volatile) 68 | 69 | odds = { 70 | "sum_under_10": (1 / prob_under_10) * (1 + house_edges["sum_under_10"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 71 | "sum_10_20": (1 / prob_10_20) * (1 + house_edges["sum_10_20"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 72 | "sum_over_20": (1 / prob_over_20) * (1 + house_edges["sum_over_20"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 73 | "all_same_suit": (1 / prob_same_suit) * (1 + house_edges["all_same_suit"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)), 74 | "all_face_cards": (1 / prob_all_face) * (1 + house_edges["all_face_cards"]) * (1 + random.uniform(-market_fluctuation, market_fluctuation)) 75 | } 76 | 77 | return odds 78 | 79 | def place_market_trade(self, amount: float, is_buy: bool) -> bool: 80 | """Place a trade with the market maker""" 81 | if amount <= 0: 82 | return False 83 | 84 | # Store the initial price for PnL calculation 85 | bid, ask = self.market_maker.get_prices() 86 | initial_price = ask if is_buy else bid 87 | 88 | # Calculate the cost of the trade (amount of shares * price per share) 89 | trade_cost = amount * initial_price 90 | 91 | # Check if player has enough balance for the trade 92 | if trade_cost > self.player_balance: 93 | return False 94 | 95 | success = self.market_maker.place_trade(amount, is_buy) 96 | if success: 97 | # Deduct the actual cost (not just the number of shares) 98 | self.player_balance -= trade_cost 99 | # Store amount, direction and initial price 100 | self.mm_trades.append((amount, is_buy, initial_price)) 101 | return True 102 | return False 103 | 104 | def get_market_prices(self) -> Tuple[float, float]: 105 | """Get current market maker bid/ask prices""" 106 | return self.market_maker.get_prices() 107 | 108 | def play_round(self) -> Dict[str, Any]: 109 | # Reset deck and shuffle 110 | self.deck = self._create_deck() 111 | self._shuffle_deck() 112 | # Draw cards 113 | drawn_cards = [self.deck.pop() for _ in range(self.num_cards)] 114 | 115 | # Calculate sum of card values 116 | total = sum(self._card_value(card) for card in drawn_cards) 117 | 118 | # Check all outcomes 119 | outcomes = { 120 | "sum_under_10": total < 10, 121 | "sum_10_20": 10 <= total <= 20, 122 | "sum_over_20": total > 20, 123 | "all_same_suit": len(set(card[1] for card in drawn_cards)) == 1, 124 | "all_face_cards": all(card[0] in ['J', 'Q', 'K'] for card in drawn_cards) 125 | } 126 | 127 | # Process regular bet winnings 128 | for outcome, won in outcomes.items(): 129 | if outcome in self.active_bets: 130 | bet_amount = self.active_bets[outcome] 131 | if won: 132 | # Return original bet plus winnings 133 | self.player_balance += bet_amount + (bet_amount * (self.current_odds[outcome] - 1)) 134 | # If bet lost, money is already deducted when bet was placed 135 | 136 | # Process market maker trades 137 | bid, ask = self.market_maker.update_prices(total) 138 | for amount, is_buy, initial_price in self.mm_trades: 139 | # Calculate original investment 140 | original_investment = amount * initial_price 141 | 142 | if is_buy: # Player bought (bet high) 143 | # Calculate PnL based on difference from initial price 144 | pnl = amount * (total - initial_price) 145 | self.player_balance += original_investment + pnl 146 | else: # Player sold (bet low) 147 | # Calculate PnL based on difference from initial price 148 | pnl = amount * (initial_price - total) 149 | self.player_balance += original_investment + pnl 150 | 151 | results = { 152 | "cards": drawn_cards, 153 | "sum": total, 154 | "outcomes": outcomes, 155 | "market_prices": (bid, ask) 156 | } 157 | 158 | # Clear bets and trades after round 159 | self.clear_bets() 160 | self.mm_trades.clear() 161 | 162 | # Recalculate odds for next round 163 | self.current_odds = self.calculate_odds() 164 | 165 | return results -------------------------------------------------------------------------------- /market_maker/ui.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk, messagebox 3 | from typing import Dict, Any, Optional 4 | from .games.dice import DiceGame 5 | from .games.poker import PokerGame 6 | from .games.coin import CoinGame 7 | from .market_maker import MarketMaker 8 | 9 | class GameUI: 10 | def __init__(self, root): 11 | self.root = root 12 | self.root.title("Market Making Trading Games") 13 | self.root.geometry("400x800") # Increased window size 14 | 15 | # Initialize all games 16 | self.dice_game = DiceGame() 17 | self.poker_game = PokerGame() 18 | self.coin_game = CoinGame() 19 | self.player_balance = 1000.0 20 | 21 | # Set initial balance for all games 22 | self.dice_game.set_balance(self.player_balance) 23 | self.poker_game.set_balance(self.player_balance) 24 | self.coin_game.set_balance(self.player_balance) 25 | 26 | self.setup_ui() 27 | 28 | def setup_ui(self): 29 | # Main container 30 | self.main_frame = ttk.Frame(self.root, padding="10") 31 | self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) 32 | 33 | # Balance and PnL display at the top 34 | self.balance_frame = ttk.Frame(self.main_frame) 35 | self.balance_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5) 36 | ttk.Label(self.balance_frame, text="Balance: $").grid(row=0, column=0) 37 | self.balance_label = ttk.Label(self.balance_frame, text=str(self.player_balance)) 38 | self.balance_label.grid(row=0, column=1) 39 | 40 | # Add PnL display 41 | ttk.Label(self.balance_frame, text=" PnL: $").grid(row=0, column=2, padx=(20,0)) 42 | self.pnl_label = ttk.Label(self.balance_frame, text="0.00") 43 | self.pnl_label.grid(row=0, column=3) 44 | 45 | # Create frames for each game - now vertically stacked 46 | self.setup_dice_section() 47 | self.setup_poker_section() 48 | self.setup_coin_section() 49 | 50 | # Initialize odds display for all games 51 | self.update_odds_display("dice") 52 | self.update_odds_display("poker") 53 | self.update_odds_display("coin") 54 | 55 | # Add submit button at the bottom for all games 56 | submit_frame = ttk.Frame(self.main_frame) 57 | submit_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=10) 58 | ttk.Button(submit_frame, text="Submit", 59 | command=self.submit_all_games).grid(row=0, column=0, sticky=(tk.W, tk.E)) 60 | 61 | def setup_dice_section(self): 62 | # Create a frame for dice game that will contain both betting and results 63 | dice_container = ttk.LabelFrame(self.main_frame, text="Dice Game", padding="5") 64 | dice_container.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5) 65 | 66 | # Create separate frames for betting and results 67 | dice_betting_frame = ttk.Frame(dice_container) 68 | dice_betting_frame.grid(row=0, column=0, padx=5) 69 | 70 | dice_results_frame = ttk.Frame(dice_container) 71 | dice_results_frame.grid(row=0, column=1, padx=5) 72 | 73 | # Initialize odds labels dictionary 74 | self.dice_odds_labels = {} 75 | self.dice_bet_amounts = {} 76 | 77 | # Setup betting panel 78 | for i, (outcome, desc) in enumerate(self.dice_game.outcomes.items()): 79 | ttk.Label(dice_betting_frame, text=f"{desc}:").grid(row=i, column=0, sticky=tk.W, padx=(5,10)) 80 | 81 | odds_value = self.dice_game.current_odds[outcome] 82 | self.dice_odds_labels[outcome] = ttk.Label(dice_betting_frame, text=f"Odds: {odds_value:.1f}:1") 83 | self.dice_odds_labels[outcome].grid(row=i, column=1, sticky=tk.W) 84 | 85 | bet_frame = ttk.Frame(dice_betting_frame) 86 | bet_frame.grid(row=i, column=2, padx=5) 87 | 88 | ttk.Label(bet_frame, text="$").grid(row=0, column=0) 89 | bet_amount = ttk.Entry(bet_frame, width=8) 90 | bet_amount.insert(0, "0") 91 | bet_amount.grid(row=0, column=1) 92 | self.dice_bet_amounts[outcome] = bet_amount 93 | 94 | # Setup results panel 95 | ttk.Label(dice_results_frame, text="Results:").grid(row=0, column=0, sticky=tk.W) 96 | self.dice_results_label = ttk.Label(dice_results_frame, text="", justify=tk.LEFT) 97 | self.dice_results_label.grid(row=1, column=0, sticky=tk.W) 98 | 99 | def setup_poker_section(self): 100 | # Create a frame for poker game that will contain both betting and results 101 | poker_container = ttk.LabelFrame(self.main_frame, text="Poker Game", padding="5") 102 | poker_container.grid(row=2, column=0, sticky="we", pady=5) 103 | 104 | # Create separate frames for betting, market maker, and results 105 | poker_betting_frame = ttk.Frame(poker_container) 106 | poker_betting_frame.grid(row=0, column=0, padx=5) 107 | 108 | poker_market_frame = ttk.Frame(poker_container) 109 | poker_market_frame.grid(row=1, column=0, padx=5, pady=10) 110 | 111 | poker_results_frame = ttk.Frame(poker_container) 112 | poker_results_frame.grid(row=0, column=1, rowspan=2, padx=5) 113 | 114 | # Initialize odds labels dictionary 115 | self.poker_odds_labels = {} 116 | self.poker_bet_amounts = {} 117 | 118 | # Setup betting panel 119 | for i, (outcome, desc) in enumerate(self.poker_game.outcomes.items()): 120 | ttk.Label(poker_betting_frame, text=f"{desc}:").grid(row=i, column=0, sticky="w", padx=(5,10)) 121 | 122 | odds_value = self.poker_game.current_odds[outcome] 123 | self.poker_odds_labels[outcome] = ttk.Label(poker_betting_frame, text=f"Odds: {odds_value:.1f}:1") 124 | self.poker_odds_labels[outcome].grid(row=i, column=1, sticky="w") 125 | 126 | bet_frame = ttk.Frame(poker_betting_frame) 127 | bet_frame.grid(row=i, column=2, padx=5) 128 | 129 | ttk.Label(bet_frame, text="$").grid(row=0, column=0) 130 | bet_amount = ttk.Entry(bet_frame, width=8) 131 | bet_amount.insert(0, "0") 132 | bet_amount.grid(row=0, column=1) 133 | self.poker_bet_amounts[outcome] = bet_amount 134 | 135 | # Setup market maker panel 136 | ttk.Label(poker_market_frame, text="Market Making - Sum of Cards").grid(row=0, column=0, columnspan=4, sticky="w") 137 | 138 | # Market maker trading section 139 | market_frame = ttk.Frame(poker_market_frame) 140 | market_frame.grid(row=1, column=0, padx=5) 141 | 142 | # Bid/Ask display 143 | self.poker_bid_label = ttk.Label(market_frame, text="Bid: 10.00") 144 | self.poker_bid_label.grid(row=0, column=0, padx=5) 145 | 146 | self.poker_ask_label = ttk.Label(market_frame, text="Ask: 11.00") 147 | self.poker_ask_label.grid(row=0, column=1, padx=5) 148 | 149 | # Amount entry 150 | ttk.Label(market_frame, text="Shares:").grid(row=0, column=2, padx=5) 151 | self.poker_trade_amount = ttk.Entry(market_frame, width=8) 152 | self.poker_trade_amount.insert(0, "0") 153 | self.poker_trade_amount.grid(row=0, column=3, padx=5) 154 | 155 | # Buy/Sell selection 156 | self.poker_trade_type = tk.StringVar(value="buy") 157 | ttk.Radiobutton(market_frame, text="Buy", value="buy", 158 | variable=self.poker_trade_type).grid(row=0, column=4, padx=5) 159 | ttk.Radiobutton(market_frame, text="Sell", value="sell", 160 | variable=self.poker_trade_type).grid(row=0, column=5, padx=5) 161 | 162 | # Setup results panel 163 | ttk.Label(poker_results_frame, text="Results:").grid(row=0, column=0, sticky="w") 164 | self.poker_results_label = ttk.Label(poker_results_frame, text="", justify="left") 165 | self.poker_results_label.grid(row=1, column=0, sticky="w") 166 | 167 | def setup_coin_section(self): 168 | # Create a frame for coin game that will contain both betting and results 169 | coin_container = ttk.LabelFrame(self.main_frame, text="Coin Flip", padding="5") 170 | coin_container.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=5) 171 | 172 | # Create separate frames for betting and results 173 | coin_betting_frame = ttk.Frame(coin_container) 174 | coin_betting_frame.grid(row=0, column=0, padx=5) 175 | 176 | coin_results_frame = ttk.Frame(coin_container) 177 | coin_results_frame.grid(row=0, column=1, padx=5) 178 | 179 | # Initialize odds labels dictionary 180 | self.coin_odds_labels = {} 181 | self.coin_bet_amounts = {} 182 | 183 | # Setup betting panel 184 | for i, (outcome, desc) in enumerate(self.coin_game.outcomes.items()): 185 | ttk.Label(coin_betting_frame, text=f"{desc}:").grid(row=i, column=0, sticky=tk.W, padx=(5,10)) 186 | 187 | odds_value = self.coin_game.current_odds[outcome] 188 | self.coin_odds_labels[outcome] = ttk.Label(coin_betting_frame, text=f"Odds: {odds_value:.1f}:1") 189 | self.coin_odds_labels[outcome].grid(row=i, column=1, sticky=tk.W) 190 | 191 | bet_frame = ttk.Frame(coin_betting_frame) 192 | bet_frame.grid(row=i, column=2, padx=5) 193 | 194 | ttk.Label(bet_frame, text="$").grid(row=0, column=0) 195 | bet_amount = ttk.Entry(bet_frame, width=8) 196 | bet_amount.insert(0, "0") 197 | bet_amount.grid(row=0, column=1) 198 | self.coin_bet_amounts[outcome] = bet_amount 199 | 200 | # Setup results panel 201 | ttk.Label(coin_results_frame, text="Results:").grid(row=0, column=0, sticky=tk.W) 202 | self.coin_results_label = ttk.Label(coin_results_frame, text="", justify=tk.LEFT) 203 | self.coin_results_label.grid(row=1, column=0, sticky=tk.W) 204 | 205 | def update_odds_display(self, game_type): 206 | if game_type == "dice": 207 | for outcome, odds in self.dice_game.current_odds.items(): 208 | if outcome in self.dice_odds_labels: 209 | self.dice_odds_labels[outcome].config(text=f"Odds: {odds:.1f}:1") 210 | elif game_type == "poker": 211 | for outcome, odds in self.poker_game.current_odds.items(): 212 | if outcome in self.poker_odds_labels: 213 | self.poker_odds_labels[outcome].config(text=f"Odds: {odds:.1f}:1") 214 | elif game_type == "coin": 215 | for outcome, odds in self.coin_game.current_odds.items(): 216 | if outcome in self.coin_odds_labels: 217 | self.coin_odds_labels[outcome].config(text=f"Odds: {odds:.1f}:1") 218 | 219 | def submit_all_games(self): 220 | try: 221 | has_activity = False 222 | initial_balance = self.player_balance 223 | result_summary = "" 224 | 225 | # Store initial balances for each game BEFORE placing any bets 226 | dice_initial = self.dice_game.player_balance 227 | poker_initial = self.poker_game.player_balance 228 | coin_initial = self.coin_game.player_balance 229 | 230 | # Process market maker trade for poker 231 | trade_amount = float(self.poker_trade_amount.get() or 0) 232 | if trade_amount > 0: 233 | has_activity = True 234 | is_buy = self.poker_trade_type.get() == "buy" 235 | success = self.poker_game.place_market_trade(trade_amount, is_buy) 236 | if not success: 237 | messagebox.showerror("Error", "Trade failed") 238 | return 239 | 240 | # Collect and validate all bets 241 | total_bet_amount = trade_amount # Include market maker trade amount 242 | 243 | # Update empty entries to show "0" 244 | for bet_dict in [self.dice_bet_amounts, self.poker_bet_amounts, self.coin_bet_amounts]: 245 | for bet_entry in bet_dict.values(): 246 | if not bet_entry.get(): # If entry is empty 247 | bet_entry.insert(0, "0") 248 | 249 | # Place all bets before playing rounds 250 | for game_bets, game in [(self.dice_bet_amounts, self.dice_game), 251 | (self.poker_bet_amounts, self.poker_game), 252 | (self.coin_bet_amounts, self.coin_game)]: 253 | for outcome, bet_entry in game_bets.items(): 254 | amount = float(bet_entry.get() or 0) 255 | if amount > 0: 256 | has_activity = True 257 | total_bet_amount += amount 258 | success = game.place_bet(outcome, amount) 259 | if not success: 260 | messagebox.showerror("Error", f"Failed to place bet on {outcome}") 261 | return 262 | 263 | if not has_activity: 264 | messagebox.showerror("Error", "Please place at least one bet or market maker trade") 265 | return 266 | 267 | # Validate total costs against balance 268 | if total_bet_amount > self.player_balance: 269 | messagebox.showerror("Error", "Insufficient balance for all bets") 270 | return 271 | 272 | # Play all games and update results 273 | game_results = { 274 | "dice": self.dice_game.play_round(), 275 | "poker": self.poker_game.play_round(), 276 | "coin": self.coin_game.play_round() 277 | } 278 | 279 | # Calculate wins/losses from each game 280 | dice_pnl = self.dice_game.player_balance - dice_initial 281 | poker_pnl = self.poker_game.player_balance - poker_initial 282 | coin_pnl = self.coin_game.player_balance - coin_initial 283 | 284 | # Update total balance with all games' PnL 285 | total_pnl = dice_pnl + poker_pnl + coin_pnl 286 | final_balance = initial_balance + total_pnl 287 | 288 | # Update UI with correct PnL and balance 289 | self.player_balance = final_balance 290 | self.balance_label.config(text=f"{self.player_balance:.2f}") 291 | self.pnl_label.config(text=f"{total_pnl:+.2f}") 292 | 293 | # Now sync the balances to prepare for next round 294 | self.sync_game_balances() 295 | 296 | # Update result summary 297 | result_summary = "Game Results:\n" 298 | if dice_pnl != 0: 299 | result_summary += f"Dice Game PnL: ${dice_pnl:+.2f}\n" 300 | if poker_pnl != 0: 301 | result_summary += f"Poker Game PnL: ${poker_pnl:+.2f}\n" 302 | if trade_amount > 0: 303 | result_summary += f"Market Maker Trade: {'Buy' if is_buy else 'Sell'} {trade_amount} shares\n" 304 | result_summary += f"Final Card Sum: {game_results['poker']['sum']}\n" 305 | if coin_pnl != 0: 306 | result_summary += f"Coin Game PnL: ${coin_pnl:+.2f}\n" 307 | 308 | result_summary += f"\nTotal PnL: ${total_pnl:+.2f}" 309 | messagebox.showinfo("Results", result_summary) 310 | 311 | # Display results for each game 312 | for game_type, result in game_results.items(): 313 | result_text = self.format_results(game_type, result) 314 | if game_type == "dice": 315 | self.dice_results_label.config(text=result_text) 316 | elif game_type == "poker": 317 | self.poker_results_label.config(text=result_text) 318 | elif game_type == "coin": 319 | self.coin_results_label.config(text=result_text) 320 | 321 | # After processing all games, update all odds displays 322 | self.update_odds_display("dice") 323 | self.update_odds_display("poker") 324 | self.update_odds_display("coin") 325 | 326 | # Reset all bet entries to "0" 327 | for bet_dict in [self.dice_bet_amounts, self.poker_bet_amounts, self.coin_bet_amounts]: 328 | for bet_entry in bet_dict.values(): 329 | bet_entry.delete(0, tk.END) # Clear current value 330 | bet_entry.insert(0, "0") # Set to "0" 331 | 332 | except ValueError: 333 | messagebox.showerror("Error", "Please enter valid amounts") 334 | 335 | def format_results(self, game_type, results): 336 | result_text = "" 337 | if game_type == "dice": 338 | result_text = f"Dice rolls: {results['dice_rolls']}\n" 339 | result_text += f"Total: {results['total']}\n" 340 | elif game_type == "poker": 341 | cards_text = [f"{card[0]} of {card[1]}" for card in results['cards']] 342 | result_text = f"Cards drawn: {', '.join(cards_text)}\n" 343 | result_text += f"Sum of cards: {results['sum']}\n" 344 | 345 | # Update bid/ask labels directly instead of showing in results 346 | bid, ask = results['market_prices'] 347 | self.poker_bid_label.config(text=f"Bid: {bid:.2f}") 348 | self.poker_ask_label.config(text=f"Ask: {ask:.2f}") 349 | elif game_type == "coin": 350 | result_text = f"Coin flips: {' '.join(results['flips'])}\n" 351 | 352 | result_text += "Outcomes:\n" 353 | for outcome, won in results['outcomes'].items(): 354 | result_text += f"{outcome}: {'Won' if won else 'Lost'}\n" 355 | 356 | return result_text 357 | 358 | def sync_game_balances(self): 359 | """Synchronize all game balances with the UI balance""" 360 | self.dice_game.set_balance(self.player_balance) 361 | self.poker_game.set_balance(self.player_balance) 362 | self.coin_game.set_balance(self.player_balance) 363 | 364 | def main(): 365 | root = tk.Tk() 366 | app = GameUI(root) 367 | root.mainloop() 368 | 369 | if __name__ == "__main__": 370 | main() --------------------------------------------------------------------------------