Event layout page update.
This commit is contained in:
436
backend/simulate_activity.py
Executable file
436
backend/simulate_activity.py
Executable file
@ -0,0 +1,436 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Activity Simulation Script
|
||||
Simulates random user activity including:
|
||||
- New user registrations
|
||||
- Creating spread bets
|
||||
- Taking/matching bets
|
||||
- Adding comments to events and matches
|
||||
- Cancelling bets
|
||||
|
||||
Usage:
|
||||
python simulate_activity.py [--iterations N] [--delay SECONDS] [--continuous]
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import argparse
|
||||
import random
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
from sqlalchemy import select, and_
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.database import async_session, init_db
|
||||
from app.models import (
|
||||
User, Wallet, SportEvent, SpreadBet, EventComment, MatchComment,
|
||||
EventStatus, SpreadBetStatus, TeamSide, Transaction, TransactionType, TransactionStatus
|
||||
)
|
||||
from app.utils.security import get_password_hash
|
||||
|
||||
# Sample data for generating random users
|
||||
FIRST_NAMES = [
|
||||
"James", "Emma", "Liam", "Olivia", "Noah", "Ava", "Oliver", "Sophia",
|
||||
"Elijah", "Isabella", "Lucas", "Mia", "Mason", "Charlotte", "Ethan",
|
||||
"Amelia", "Alexander", "Harper", "Henry", "Evelyn", "Sebastian", "Luna",
|
||||
"Jack", "Camila", "Aiden", "Gianna", "Owen", "Abigail", "Samuel", "Ella",
|
||||
"Ryan", "Scarlett", "Nathan", "Emily", "Caleb", "Elizabeth", "Hunter",
|
||||
"Sofia", "Christian", "Avery", "Landon", "Chloe", "Jonathan", "Victoria"
|
||||
]
|
||||
|
||||
LAST_NAMES = [
|
||||
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
|
||||
"Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez",
|
||||
"Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
|
||||
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark",
|
||||
"Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King"
|
||||
]
|
||||
|
||||
# Sample comments for events
|
||||
EVENT_COMMENTS = [
|
||||
"This is going to be a great game!",
|
||||
"Home team looking strong this season",
|
||||
"I'm betting on the underdog here",
|
||||
"What do you all think about the spread?",
|
||||
"Last time these teams played it was close",
|
||||
"Weather might be a factor today",
|
||||
"Key player is out, that changes everything",
|
||||
"The odds seem off to me",
|
||||
"Anyone else feeling bullish on the away team?",
|
||||
"This matchup is always entertaining",
|
||||
"Line moved a lot overnight",
|
||||
"Sharp money coming in on the home side",
|
||||
"Public is heavy on the favorite",
|
||||
"Value play on the underdog here",
|
||||
"Injury report looking concerning",
|
||||
"Home field advantage is huge here",
|
||||
"Expecting a low-scoring affair",
|
||||
"Over/under seems too high",
|
||||
"Rivalry game, throw out the records!",
|
||||
"Coach has a great record against this opponent"
|
||||
]
|
||||
|
||||
# Sample comments for matches (between two bettors)
|
||||
MATCH_COMMENTS = [
|
||||
"Good luck!",
|
||||
"May the best bettor win",
|
||||
"This should be interesting",
|
||||
"I'm feeling confident about this one",
|
||||
"Let's see how this plays out",
|
||||
"Nice bet, looking forward to the game",
|
||||
"GL HF",
|
||||
"First time betting against you",
|
||||
"Rematch from last week!",
|
||||
"I've been waiting for this matchup",
|
||||
"Your team doesn't stand a chance!",
|
||||
"We'll see about that...",
|
||||
"Close game incoming",
|
||||
"I'll be watching every play",
|
||||
"Don't count your winnings yet!"
|
||||
]
|
||||
|
||||
|
||||
async def create_random_user(db) -> User | None:
|
||||
"""Create a new random user with a wallet."""
|
||||
first = random.choice(FIRST_NAMES)
|
||||
last = random.choice(LAST_NAMES)
|
||||
|
||||
# Generate unique username
|
||||
suffix = random.randint(100, 9999)
|
||||
username = f"{first.lower()}{last.lower()}{suffix}"
|
||||
email = f"{username}@example.com"
|
||||
|
||||
# Check if user already exists
|
||||
existing = await db.execute(
|
||||
select(User).where(User.username == username)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
return None
|
||||
|
||||
user = User(
|
||||
email=email,
|
||||
username=username,
|
||||
password_hash=get_password_hash("password123"),
|
||||
display_name=f"{first} {last}"
|
||||
)
|
||||
db.add(user)
|
||||
await db.flush()
|
||||
|
||||
# Create wallet with random starting balance
|
||||
starting_balance = Decimal(str(random.randint(500, 5000)))
|
||||
wallet = Wallet(
|
||||
user_id=user.id,
|
||||
balance=starting_balance,
|
||||
escrow=Decimal("0.00")
|
||||
)
|
||||
db.add(wallet)
|
||||
await db.commit()
|
||||
|
||||
print(f" [USER] Created user: {user.username} with ${starting_balance} balance")
|
||||
return user
|
||||
|
||||
|
||||
async def create_random_bet(db, users: list[User], events: list[SportEvent]) -> SpreadBet | None:
|
||||
"""Create a random spread bet on an event."""
|
||||
if not users or not events:
|
||||
return None
|
||||
|
||||
# Filter for users with sufficient balance
|
||||
users_with_balance = []
|
||||
for user in users:
|
||||
wallet_result = await db.execute(
|
||||
select(Wallet).where(Wallet.user_id == user.id)
|
||||
)
|
||||
wallet = wallet_result.scalar_one_or_none()
|
||||
if wallet and wallet.balance >= Decimal("10"):
|
||||
users_with_balance.append((user, wallet))
|
||||
|
||||
if not users_with_balance:
|
||||
return None
|
||||
|
||||
user, wallet = random.choice(users_with_balance)
|
||||
event = random.choice(events)
|
||||
|
||||
# Random spread within event range
|
||||
spread = round(random.uniform(event.min_spread, event.max_spread) * 2) / 2 # Round to 0.5
|
||||
|
||||
# Random stake (10-50% of balance, max $500)
|
||||
max_stake = min(float(wallet.balance) * 0.5, 500)
|
||||
stake = Decimal(str(round(random.uniform(10, max(10, max_stake)), 2)))
|
||||
|
||||
# Random team selection
|
||||
team = random.choice([TeamSide.HOME, TeamSide.AWAY])
|
||||
|
||||
bet = SpreadBet(
|
||||
event_id=event.id,
|
||||
spread=spread,
|
||||
team=team,
|
||||
creator_id=user.id,
|
||||
stake_amount=stake,
|
||||
house_commission_percent=Decimal("10.00"),
|
||||
status=SpreadBetStatus.OPEN
|
||||
)
|
||||
db.add(bet)
|
||||
await db.commit()
|
||||
|
||||
team_name = event.home_team if team == TeamSide.HOME else event.away_team
|
||||
print(f" [BET] {user.username} created ${stake} bet on {team_name} {'+' if spread > 0 else ''}{spread}")
|
||||
return bet
|
||||
|
||||
|
||||
async def take_random_bet(db, users: list[User]) -> SpreadBet | None:
|
||||
"""Have a random user take an open bet."""
|
||||
# Get open bets
|
||||
result = await db.execute(
|
||||
select(SpreadBet)
|
||||
.options(selectinload(SpreadBet.event), selectinload(SpreadBet.creator))
|
||||
.where(SpreadBet.status == SpreadBetStatus.OPEN)
|
||||
)
|
||||
open_bets = result.scalars().all()
|
||||
|
||||
if not open_bets:
|
||||
return None
|
||||
|
||||
bet = random.choice(open_bets)
|
||||
|
||||
# Find eligible takers (not creator, has balance)
|
||||
eligible_takers = []
|
||||
for user in users:
|
||||
if user.id == bet.creator_id:
|
||||
continue
|
||||
wallet_result = await db.execute(
|
||||
select(Wallet).where(Wallet.user_id == user.id)
|
||||
)
|
||||
wallet = wallet_result.scalar_one_or_none()
|
||||
if wallet and wallet.balance >= bet.stake_amount:
|
||||
eligible_takers.append((user, wallet))
|
||||
|
||||
if not eligible_takers:
|
||||
return None
|
||||
|
||||
taker, taker_wallet = random.choice(eligible_takers)
|
||||
|
||||
# Get creator's wallet
|
||||
creator_wallet_result = await db.execute(
|
||||
select(Wallet).where(Wallet.user_id == bet.creator_id)
|
||||
)
|
||||
creator_wallet = creator_wallet_result.scalar_one_or_none()
|
||||
|
||||
if not creator_wallet or creator_wallet.balance < bet.stake_amount:
|
||||
return None
|
||||
|
||||
# Lock funds for both parties
|
||||
creator_wallet.balance -= bet.stake_amount
|
||||
creator_wallet.escrow += bet.stake_amount
|
||||
taker_wallet.balance -= bet.stake_amount
|
||||
taker_wallet.escrow += bet.stake_amount
|
||||
|
||||
# Create transactions
|
||||
creator_tx = Transaction(
|
||||
user_id=bet.creator_id,
|
||||
wallet_id=creator_wallet.id,
|
||||
type=TransactionType.ESCROW_LOCK,
|
||||
amount=-bet.stake_amount,
|
||||
balance_after=creator_wallet.balance,
|
||||
reference_id=bet.id,
|
||||
description=f"Escrow lock for spread bet #{bet.id}",
|
||||
status=TransactionStatus.COMPLETED
|
||||
)
|
||||
taker_tx = Transaction(
|
||||
user_id=taker.id,
|
||||
wallet_id=taker_wallet.id,
|
||||
type=TransactionType.ESCROW_LOCK,
|
||||
amount=-bet.stake_amount,
|
||||
balance_after=taker_wallet.balance,
|
||||
reference_id=bet.id,
|
||||
description=f"Escrow lock for spread bet #{bet.id}",
|
||||
status=TransactionStatus.COMPLETED
|
||||
)
|
||||
db.add(creator_tx)
|
||||
db.add(taker_tx)
|
||||
|
||||
# Update bet
|
||||
bet.taker_id = taker.id
|
||||
bet.status = SpreadBetStatus.MATCHED
|
||||
bet.matched_at = datetime.utcnow()
|
||||
|
||||
await db.commit()
|
||||
|
||||
print(f" [MATCH] {taker.username} took {bet.creator.username}'s ${bet.stake_amount} bet")
|
||||
return bet
|
||||
|
||||
|
||||
async def cancel_random_bet(db) -> SpreadBet | None:
|
||||
"""Cancel a random open bet."""
|
||||
result = await db.execute(
|
||||
select(SpreadBet)
|
||||
.options(selectinload(SpreadBet.creator))
|
||||
.where(SpreadBet.status == SpreadBetStatus.OPEN)
|
||||
)
|
||||
open_bets = result.scalars().all()
|
||||
|
||||
if not open_bets:
|
||||
return None
|
||||
|
||||
# 20% chance to cancel
|
||||
if random.random() > 0.2:
|
||||
return None
|
||||
|
||||
bet = random.choice(open_bets)
|
||||
bet.status = SpreadBetStatus.CANCELLED
|
||||
await db.commit()
|
||||
|
||||
print(f" [CANCEL] {bet.creator.username} cancelled their ${bet.stake_amount} bet")
|
||||
return bet
|
||||
|
||||
|
||||
async def add_event_comment(db, users: list[User], events: list[SportEvent]) -> EventComment | None:
|
||||
"""Add a random comment to an event."""
|
||||
if not users or not events:
|
||||
return None
|
||||
|
||||
user = random.choice(users)
|
||||
event = random.choice(events)
|
||||
content = random.choice(EVENT_COMMENTS)
|
||||
|
||||
comment = EventComment(
|
||||
event_id=event.id,
|
||||
user_id=user.id,
|
||||
content=content
|
||||
)
|
||||
db.add(comment)
|
||||
await db.commit()
|
||||
|
||||
print(f" [COMMENT] {user.username} on {event.home_team} vs {event.away_team}: \"{content[:40]}...\"")
|
||||
return comment
|
||||
|
||||
|
||||
async def add_match_comment(db, users: list[User]) -> MatchComment | None:
|
||||
"""Add a random comment to a matched bet."""
|
||||
# Get matched bets
|
||||
result = await db.execute(
|
||||
select(SpreadBet)
|
||||
.options(selectinload(SpreadBet.creator), selectinload(SpreadBet.taker))
|
||||
.where(SpreadBet.status == SpreadBetStatus.MATCHED)
|
||||
)
|
||||
matched_bets = result.scalars().all()
|
||||
|
||||
if not matched_bets:
|
||||
return None
|
||||
|
||||
bet = random.choice(matched_bets)
|
||||
|
||||
# Comment from either creator or taker
|
||||
user = random.choice([bet.creator, bet.taker])
|
||||
content = random.choice(MATCH_COMMENTS)
|
||||
|
||||
comment = MatchComment(
|
||||
spread_bet_id=bet.id,
|
||||
user_id=user.id,
|
||||
content=content
|
||||
)
|
||||
db.add(comment)
|
||||
await db.commit()
|
||||
|
||||
print(f" [CHAT] {user.username} in match #{bet.id}: \"{content}\"")
|
||||
return comment
|
||||
|
||||
|
||||
async def run_simulation(iterations: int = 10, delay: float = 2.0, continuous: bool = False):
|
||||
"""Run the activity simulation."""
|
||||
print("=" * 60)
|
||||
print("H2H Activity Simulator")
|
||||
print("=" * 60)
|
||||
|
||||
await init_db()
|
||||
|
||||
iteration = 0
|
||||
while continuous or iteration < iterations:
|
||||
iteration += 1
|
||||
print(f"\n--- Iteration {iteration} ---")
|
||||
|
||||
async with async_session() as db:
|
||||
# Get existing users and events
|
||||
users_result = await db.execute(select(User))
|
||||
users = list(users_result.scalars().all())
|
||||
|
||||
events_result = await db.execute(
|
||||
select(SportEvent).where(SportEvent.status == EventStatus.UPCOMING)
|
||||
)
|
||||
events = list(events_result.scalars().all())
|
||||
|
||||
if not events:
|
||||
print(" No upcoming events found. Run manage_events.py first.")
|
||||
if not continuous:
|
||||
break
|
||||
await asyncio.sleep(delay)
|
||||
continue
|
||||
|
||||
# Random actions with weighted probabilities
|
||||
actions = [
|
||||
(create_random_user, 0.15), # 15% - Create user
|
||||
(lambda db: create_random_bet(db, users, events), 0.30), # 30% - Create bet
|
||||
(lambda db: take_random_bet(db, users), 0.20), # 20% - Take bet
|
||||
(lambda db: cancel_random_bet(db), 0.05), # 5% - Cancel bet
|
||||
(lambda db: add_event_comment(db, users, events), 0.15), # 15% - Event comment
|
||||
(lambda db: add_match_comment(db, users), 0.15), # 15% - Match comment
|
||||
]
|
||||
|
||||
# Perform 1-3 random actions per iteration
|
||||
num_actions = random.randint(1, 3)
|
||||
for _ in range(num_actions):
|
||||
# Weighted random selection
|
||||
rand = random.random()
|
||||
cumulative = 0
|
||||
for action_fn, probability in actions:
|
||||
cumulative += probability
|
||||
if rand <= cumulative:
|
||||
try:
|
||||
if action_fn == create_random_user:
|
||||
await action_fn(db)
|
||||
else:
|
||||
await action_fn(db)
|
||||
except Exception as e:
|
||||
print(f" [ERROR] {e}")
|
||||
break
|
||||
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Simulation complete!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Simulate H2H platform activity")
|
||||
parser.add_argument(
|
||||
"--iterations", "-n",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Number of simulation iterations (default: 10)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delay", "-d",
|
||||
type=float,
|
||||
default=2.0,
|
||||
help="Delay between iterations in seconds (default: 2.0)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--continuous", "-c",
|
||||
action="store_true",
|
||||
help="Run continuously until interrupted"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
asyncio.run(run_simulation(
|
||||
iterations=args.iterations,
|
||||
delay=args.delay,
|
||||
continuous=args.continuous
|
||||
))
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nSimulation stopped by user.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user