Lemontropia-Suite/modules/market_prices.py

263 lines
8.8 KiB
Python

"""
Lemontropia Suite - Market Price Tracker
Fetch and track market prices from EntropiaWiki and other sources.
"""
import json
import logging
import requests
from decimal import Decimal
from pathlib import Path
from dataclasses import dataclass
from typing import Dict, Optional, List
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
@dataclass
class MarketPrice:
"""Market price data for an item."""
item_name: str
markup: Decimal # Percentage (e.g., 120.5 means 120.5%)
tt_value: Decimal # Trade Terminal value
mu_value: Decimal # Markup value
last_updated: datetime
source: str # Where the price came from
class EntropiaWikiPrices:
"""
Fetch market prices from EntropiaWiki.
Note: This requires scraping or API access.
EntropiaWiki may have rate limits.
"""
BASE_URL = "https://www.entropiawiki.com"
def __init__(self, cache_duration: int = 3600):
self.cache_duration = cache_duration # Seconds
self.price_cache: Dict[str, MarketPrice] = {}
self.cache_timestamps: Dict[str, datetime] = {}
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Lemontropia-Suite/1.0 (Personal Use)'
})
def _is_cache_valid(self, item_name: str) -> bool:
"""Check if cached price is still valid."""
if item_name not in self.cache_timestamps:
return False
age = datetime.now() - self.cache_timestamps[item_name]
return age.seconds < self.cache_duration
def get_item_price(self, item_name: str) -> Optional[MarketPrice]:
"""
Get market price for item.
Note: This is a placeholder. Actual implementation would need
to scrape EntropiaWiki or use their API if available.
"""
# Check cache first
if self._is_cache_valid(item_name):
return self.price_cache[item_name]
# Placeholder: In reality, this would fetch from wiki
# For now, return None to indicate we need real implementation
logger.debug(f"Price lookup not implemented for: {item_name}")
return None
def clear_cache(self):
"""Clear price cache."""
self.price_cache.clear()
self.cache_timestamps.clear()
class ManualPriceTracker:
"""
Manual price tracking for items you frequently trade.
"""
def __init__(self, data_dir: Optional[Path] = None):
self.data_dir = data_dir or Path.home() / ".lemontropia" / "prices"
self.data_dir.mkdir(parents=True, exist_ok=True)
self.prices: Dict[str, MarketPrice] = {}
self._load_prices()
def _load_prices(self):
"""Load saved prices."""
price_file = self.data_dir / "manual_prices.json"
if price_file.exists():
try:
with open(price_file, 'r') as f:
data = json.load(f)
for name, price_data in data.items():
self.prices[name] = MarketPrice(
item_name=name,
markup=Decimal(str(price_data.get('markup', 100))),
tt_value=Decimal(str(price_data.get('tt', 0))),
mu_value=Decimal(str(price_data.get('mu', 0))),
last_updated=datetime.fromisoformat(price_data.get('updated', datetime.now().isoformat())),
source='manual'
)
except Exception as e:
logger.error(f"Failed to load prices: {e}")
def _save_prices(self):
"""Save prices to file."""
price_file = self.data_dir / "manual_prices.json"
try:
data = {}
for name, price in self.prices.items():
data[name] = {
'markup': str(price.markup),
'tt': str(price.tt_value),
'mu': str(price.mu_value),
'updated': price.last_updated.isoformat(),
}
with open(price_file, 'w') as f:
json.dump(data, f, indent=2)
except Exception as e:
logger.error(f"Failed to save prices: {e}")
def set_price(self, item_name: str, markup: Decimal, tt_value: Decimal):
"""Manually set price for an item."""
mu_value = tt_value * (markup / 100)
self.prices[item_name] = MarketPrice(
item_name=item_name,
markup=markup,
tt_value=tt_value,
mu_value=mu_value,
last_updated=datetime.now(),
source='manual'
)
self._save_prices()
logger.info(f"Set price for {item_name}: {markup}%")
def get_price(self, item_name: str) -> Optional[MarketPrice]:
"""Get price for item."""
return self.prices.get(item_name)
def calculate_loot_value(self, loot_items: List[Tuple[str, Decimal, int]]) -> Dict:
"""
Calculate total value of loot based on tracked prices.
Args:
loot_items: List of (item_name, tt_value, quantity)
Returns:
Dict with tt_value, mu_value, markup breakdown
"""
total_tt = Decimal("0")
total_mu = Decimal("0")
unknown_items = []
for item_name, tt_value, quantity in loot_items:
item_tt = tt_value * quantity
total_tt += item_tt
price = self.get_price(item_name)
if price:
item_mu = item_tt * (price.markup / 100)
total_mu += item_mu
else:
# Assume 100% (TT value) if no price
total_mu += item_tt
unknown_items.append(item_name)
return {
'total_tt': total_tt,
'total_mu': total_mu,
'total_markup': (total_mu / total_tt * 100) if total_tt > 0 else Decimal("0"),
'unknown_items': list(set(unknown_items)),
}
class ProfitCalculator:
"""
Calculate crafting/hunting profitability with current market prices.
"""
def __init__(self, price_tracker: ManualPriceTracker):
self.price_tracker = price_tracker
def calculate_crafting_profit(self, blueprint: str,
material_costs: Dict[str, Decimal],
output_items: List[Tuple[str, Decimal, int]]) -> Dict:
"""
Calculate crafting profitability.
Args:
blueprint: Blueprint name
material_costs: Dict of material_name -> cost
output_items: List of (item_name, tt_value, quantity)
Returns:
Profit analysis
"""
# Calculate input cost
input_cost = sum(material_costs.values())
# Calculate output value
loot_value = self.price_tracker.calculate_loot_value(output_items)
output_mu = loot_value['total_mu']
# Calculate profit
profit = output_mu - input_cost
profit_margin = (profit / input_cost * 100) if input_cost > 0 else Decimal("0")
return {
'input_cost': input_cost,
'output_tt': loot_value['total_tt'],
'output_mu': output_mu,
'profit': profit,
'profit_margin': profit_margin,
'is_profitable': profit > 0,
'unknown_prices': loot_value['unknown_items'],
}
def calculate_hunting_profit(self, ammo_cost: Decimal,
armor_decay: Decimal,
healing_cost: Decimal,
loot_items: List[Tuple[str, Decimal, int]]) -> Dict:
"""
Calculate hunting profitability.
Args:
ammo_cost: Total ammo spent
armor_decay: Armor decay cost
healing_cost: Healing cost
loot_items: List of (item_name, tt_value, quantity)
> Returns:
Profit analysis
"""
total_cost = ammo_cost + armor_decay + healing_cost
loot_value = self.price_tracker.calculate_loot_value(loot_items)
loot_mu = loot_value['total_mu']
profit = loot_mu - total_cost
return_pct = (loot_mu / total_cost * 100) if total_cost > 0 else Decimal("0")
return {
'total_cost': total_cost,
'ammo_cost': ammo_cost,
'armor_decay': armor_decay,
'healing_cost': healing_cost,
'loot_tt': loot_value['total_tt'],
'loot_mu': loot_mu,
'profit': profit,
'return_pct': return_pct,
'unknown_prices': loot_value['unknown_items'],
}
# Export main classes
__all__ = ['MarketPrice', 'ManualPriceTracker', 'ProfitCalculator', 'EntropiaWikiPrices']