551 lines
11 KiB
Markdown
551 lines
11 KiB
Markdown
# Market Price System Documentation
|
|
|
|
## Overview
|
|
|
|
The market price system stores **34,641 Steam market prices** (29,602 CS2 + 5,039 Rust) in MongoDB for instant price lookups when loading inventory or updating items.
|
|
|
|
---
|
|
|
|
## Database Structure
|
|
|
|
### Collection: `marketprices`
|
|
|
|
```javascript
|
|
{
|
|
name: "AK-47 | Redline (Field-Tested)",
|
|
game: "cs2",
|
|
appId: 730,
|
|
marketHashName: "AK-47 | Redline (Field-Tested)",
|
|
price: 12.50,
|
|
priceType: "safe",
|
|
image: "https://...",
|
|
borderColor: "#eb4b4b",
|
|
nameId: 123456,
|
|
lastUpdated: ISODate("2024-01-10T12:00:00Z")
|
|
}
|
|
```
|
|
|
|
### Indexes
|
|
|
|
- `{ marketHashName: 1 }` - Unique, for exact lookups
|
|
- `{ game: 1, name: 1 }` - For game-specific name searches
|
|
- `{ game: 1, marketHashName: 1 }` - For game-specific hash lookups
|
|
- `{ game: 1, price: -1 }` - For sorting by price
|
|
- `{ lastUpdated: -1 }` - For finding outdated data
|
|
|
|
---
|
|
|
|
## Current Status
|
|
|
|
```
|
|
📊 Market Prices Database:
|
|
CS2: 29,602 items
|
|
Rust: 5,039 items
|
|
Total: 34,641 items
|
|
|
|
Top CS2 Price: $2,103.21 (StatTrak™ Bayonet | Case Hardened)
|
|
Top Rust Price: $2,019.59 (Punishment Mask)
|
|
```
|
|
|
|
---
|
|
|
|
## Usage
|
|
|
|
### Import the Service
|
|
|
|
```javascript
|
|
import marketPriceService from "./services/marketPrice.js";
|
|
```
|
|
|
|
### Get Single Price
|
|
|
|
```javascript
|
|
// By exact market hash name
|
|
const price = await marketPriceService.getPrice(
|
|
"AK-47 | Redline (Field-Tested)",
|
|
"cs2"
|
|
);
|
|
console.log(price); // 12.50
|
|
```
|
|
|
|
### Get Multiple Prices (Batch)
|
|
|
|
```javascript
|
|
const names = [
|
|
"AK-47 | Redline (Field-Tested)",
|
|
"AWP | Asiimov (Field-Tested)",
|
|
"M4A4 | Howl (Factory New)"
|
|
];
|
|
|
|
const priceMap = await marketPriceService.getPrices(names, "cs2");
|
|
console.log(priceMap);
|
|
// {
|
|
// "AK-47 | Redline (Field-Tested)": 12.50,
|
|
// "AWP | Asiimov (Field-Tested)": 95.00,
|
|
// "M4A4 | Howl (Factory New)": 4500.00
|
|
// }
|
|
```
|
|
|
|
### Get Full Item Data
|
|
|
|
```javascript
|
|
const item = await marketPriceService.getItem(
|
|
"AK-47 | Redline (Field-Tested)",
|
|
"cs2"
|
|
);
|
|
console.log(item);
|
|
// {
|
|
// name: "AK-47 | Redline (Field-Tested)",
|
|
// game: "cs2",
|
|
// price: 12.50,
|
|
// image: "https://...",
|
|
// borderColor: "#eb4b4b",
|
|
// ...
|
|
// }
|
|
```
|
|
|
|
### Enrich Inventory with Prices
|
|
|
|
```javascript
|
|
// When loading Steam inventory
|
|
const inventoryItems = [
|
|
{ market_hash_name: "AK-47 | Redline (Field-Tested)", ... },
|
|
{ market_hash_name: "AWP | Asiimov (Field-Tested)", ... }
|
|
];
|
|
|
|
const enriched = await marketPriceService.enrichInventory(
|
|
inventoryItems,
|
|
"cs2"
|
|
);
|
|
|
|
console.log(enriched[0]);
|
|
// {
|
|
// market_hash_name: "AK-47 | Redline (Field-Tested)",
|
|
// marketPrice: 12.50,
|
|
// hasPriceData: true,
|
|
// ...
|
|
// }
|
|
```
|
|
|
|
### Search Items
|
|
|
|
```javascript
|
|
// Search by name (partial match)
|
|
const results = await marketPriceService.search("AK-47", "cs2", 10);
|
|
console.log(results.length); // Up to 10 results
|
|
```
|
|
|
|
### Get Suggested Price (with Markup)
|
|
|
|
```javascript
|
|
// Get price with 10% markup
|
|
const suggested = await marketPriceService.getSuggestedPrice(
|
|
"AK-47 | Redline (Field-Tested)",
|
|
"cs2",
|
|
1.10 // 10% markup
|
|
);
|
|
console.log(suggested); // 13.75
|
|
```
|
|
|
|
### Get Price Statistics
|
|
|
|
```javascript
|
|
const stats = await marketPriceService.getStats("cs2");
|
|
console.log(stats);
|
|
// {
|
|
// count: 29602,
|
|
// avgPrice: 15.50,
|
|
// minPrice: 0.03,
|
|
// maxPrice: 2103.21,
|
|
// totalValue: 458831.00
|
|
// }
|
|
```
|
|
|
|
### Get Top Priced Items
|
|
|
|
```javascript
|
|
const topItems = await marketPriceService.getTopPriced("cs2", 10);
|
|
topItems.forEach((item, i) => {
|
|
console.log(`${i + 1}. ${item.name}: $${item.price}`);
|
|
});
|
|
```
|
|
|
|
### Get Items by Price Range
|
|
|
|
```javascript
|
|
// Get items between $10 and $50
|
|
const items = await marketPriceService.getByPriceRange(10, 50, "cs2", 100);
|
|
console.log(`Found ${items.length} items`);
|
|
```
|
|
|
|
---
|
|
|
|
## Commands
|
|
|
|
### Import/Update All Prices
|
|
|
|
```bash
|
|
# Download latest prices from Steam API and import to database
|
|
node import-market-prices.js
|
|
|
|
# This will:
|
|
# 1. Fetch 29,602 CS2 items
|
|
# 2. Fetch 5,039 Rust items
|
|
# 3. Upsert into MongoDB (updates existing, inserts new)
|
|
# 4. Takes ~30-60 seconds
|
|
```
|
|
|
|
### Check Status
|
|
|
|
```bash
|
|
# Check how many items are in database
|
|
node -e "import('./services/marketPrice.js').then(async s => {
|
|
const cs2 = await s.default.getCount('cs2');
|
|
const rust = await s.default.getCount('rust');
|
|
console.log('CS2:', cs2);
|
|
console.log('Rust:', rust);
|
|
process.exit(0);
|
|
})"
|
|
```
|
|
|
|
### Test Price Lookup
|
|
|
|
```bash
|
|
# Test looking up a specific item
|
|
node -e "import('./services/marketPrice.js').then(async s => {
|
|
const price = await s.default.getPrice('AK-47 | Redline (Field-Tested)', 'cs2');
|
|
console.log('Price:', price);
|
|
process.exit(0);
|
|
})"
|
|
```
|
|
|
|
---
|
|
|
|
## Integration Examples
|
|
|
|
### Example 1: Sell Page Inventory Loading
|
|
|
|
```javascript
|
|
// routes/inventory.js
|
|
import marketPriceService from "../services/marketPrice.js";
|
|
|
|
fastify.get("/inventory/:game", async (request, reply) => {
|
|
const { game } = request.params;
|
|
|
|
// Fetch from Steam API
|
|
const steamInventory = await fetchSteamInventory(
|
|
request.user.steamId,
|
|
game
|
|
);
|
|
|
|
// Enrich with market prices
|
|
const enrichedInventory = await marketPriceService.enrichInventory(
|
|
steamInventory,
|
|
game
|
|
);
|
|
|
|
return reply.send({
|
|
success: true,
|
|
items: enrichedInventory
|
|
});
|
|
});
|
|
```
|
|
|
|
### Example 2: Admin Panel Price Override
|
|
|
|
```javascript
|
|
// routes/admin.js
|
|
fastify.put("/items/:id/price", async (request, reply) => {
|
|
const { id } = request.params;
|
|
const { price, marketHashName } = request.body;
|
|
|
|
// Get suggested price from market data
|
|
const marketPrice = await marketPriceService.getPrice(
|
|
marketHashName,
|
|
"cs2"
|
|
);
|
|
|
|
// Update item
|
|
await Item.findByIdAndUpdate(id, {
|
|
price: price,
|
|
marketPrice: marketPrice,
|
|
priceOverride: true
|
|
});
|
|
|
|
return reply.send({ success: true });
|
|
});
|
|
```
|
|
|
|
### Example 3: Auto-Price New Listings
|
|
|
|
```javascript
|
|
// When user lists item for sale
|
|
fastify.post("/sell", async (request, reply) => {
|
|
const { assetId, game } = request.body;
|
|
|
|
// Get item from inventory
|
|
const inventoryItem = await getInventoryItem(assetId);
|
|
|
|
// Get suggested price (with 5% markup)
|
|
const suggestedPrice = await marketPriceService.getSuggestedPrice(
|
|
inventoryItem.market_hash_name,
|
|
game,
|
|
1.05 // 5% markup
|
|
);
|
|
|
|
// Create listing
|
|
const item = await Item.create({
|
|
name: inventoryItem.market_hash_name,
|
|
price: suggestedPrice,
|
|
game: game,
|
|
seller: request.user._id
|
|
});
|
|
|
|
return reply.send({
|
|
success: true,
|
|
item,
|
|
suggestedPrice
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Price Types
|
|
|
|
The system uses the best available price from Steam API:
|
|
|
|
1. **safe** (preferred) - Most reliable price
|
|
2. **median** - Middle value price
|
|
3. **mean** - Average price
|
|
4. **avg** - Alternative average
|
|
5. **latest** - Most recent transaction price
|
|
|
|
---
|
|
|
|
## Maintenance
|
|
|
|
### Update Schedule
|
|
|
|
**Recommended**: Run weekly or bi-weekly
|
|
|
|
```bash
|
|
# Add to cron or Task Scheduler
|
|
# Every Sunday at 2 AM
|
|
0 2 * * 0 cd /path/to/TurboTrades && node import-market-prices.js
|
|
```
|
|
|
|
### Check if Data is Outdated
|
|
|
|
```javascript
|
|
const isOutdated = await marketPriceService.isOutdated("cs2", 168); // 7 days
|
|
if (isOutdated) {
|
|
console.log("⚠️ Price data is older than 7 days - consider updating");
|
|
}
|
|
```
|
|
|
|
### Get Last Update Time
|
|
|
|
```javascript
|
|
const lastUpdate = await marketPriceService.getLastUpdate("cs2");
|
|
console.log(`Last updated: ${lastUpdate.toLocaleString()}`);
|
|
```
|
|
|
|
---
|
|
|
|
## Performance
|
|
|
|
### Query Performance
|
|
|
|
- ✅ **Single lookup**: < 1ms (indexed)
|
|
- ✅ **Batch lookup** (100 items): < 10ms (indexed)
|
|
- ✅ **Search** (regex): < 50ms
|
|
- ✅ **Stats aggregation**: < 100ms
|
|
|
|
### Storage
|
|
|
|
- **CS2**: ~15MB (29,602 items)
|
|
- **Rust**: ~2.5MB (5,039 items)
|
|
- **Total**: ~17.5MB
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
All service methods return `null` or empty arrays on error:
|
|
|
|
```javascript
|
|
const price = await marketPriceService.getPrice("Invalid Item", "cs2");
|
|
console.log(price); // null
|
|
|
|
const items = await marketPriceService.search("xyz", "cs2");
|
|
console.log(items); // []
|
|
```
|
|
|
|
Always check for null/empty:
|
|
|
|
```javascript
|
|
const price = await marketPriceService.getPrice(itemName, game);
|
|
if (price === null) {
|
|
console.log("Price not found - using fallback");
|
|
price = 0.00;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## API Comparison
|
|
|
|
### Old Method (Live API Call)
|
|
|
|
```javascript
|
|
// Slow: 500-2000ms per request
|
|
// Rate limited: 200 calls/minute
|
|
// Requires API key on every request
|
|
const price = await steamAPIsClient.getPrice(itemName);
|
|
```
|
|
|
|
### New Method (Database Lookup)
|
|
|
|
```javascript
|
|
// Fast: < 1ms per request
|
|
// No rate limits
|
|
// No API key needed
|
|
const price = await marketPriceService.getPrice(itemName, game);
|
|
```
|
|
|
|
**Benefits**:
|
|
- ✅ 500x faster
|
|
- ✅ No rate limits
|
|
- ✅ Works offline
|
|
- ✅ Batch lookups
|
|
- ✅ Full-text search
|
|
- ✅ Price statistics
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### No prices found
|
|
|
|
```bash
|
|
# Check if data exists
|
|
node -e "import('./services/marketPrice.js').then(async s => {
|
|
const count = await s.default.getCount();
|
|
console.log('Total items:', count);
|
|
if (count === 0) {
|
|
console.log('Run: node import-market-prices.js');
|
|
}
|
|
process.exit(0);
|
|
})"
|
|
```
|
|
|
|
### Prices outdated
|
|
|
|
```bash
|
|
# Re-import latest prices
|
|
node import-market-prices.js
|
|
```
|
|
|
|
### Item not found
|
|
|
|
The item name must match **exactly** as it appears in Steam market:
|
|
|
|
```javascript
|
|
// ❌ Wrong
|
|
"AK-47 Redline FT"
|
|
|
|
// ✅ Correct
|
|
"AK-47 | Redline (Field-Tested)"
|
|
```
|
|
|
|
Use the `search()` function to find correct names:
|
|
|
|
```javascript
|
|
const results = await marketPriceService.search("AK-47 Redline", "cs2");
|
|
console.log(results.map(r => r.name));
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Always Specify Game
|
|
|
|
```javascript
|
|
// ✅ Good - faster query
|
|
const price = await marketPriceService.getPrice(name, "cs2");
|
|
|
|
// ❌ Slower - searches all games
|
|
const price = await marketPriceService.getPrice(name);
|
|
```
|
|
|
|
### 2. Use Batch Lookups for Multiple Items
|
|
|
|
```javascript
|
|
// ✅ Good - single query
|
|
const prices = await marketPriceService.getPrices(names, game);
|
|
|
|
// ❌ Bad - multiple queries
|
|
for (const name of names) {
|
|
const price = await marketPriceService.getPrice(name, game);
|
|
}
|
|
```
|
|
|
|
### 3. Cache Frequently Accessed Prices
|
|
|
|
```javascript
|
|
// In-memory cache for hot items
|
|
const priceCache = new Map();
|
|
|
|
async function getCachedPrice(name, game) {
|
|
const key = `${game}:${name}`;
|
|
if (priceCache.has(key)) {
|
|
return priceCache.get(key);
|
|
}
|
|
|
|
const price = await marketPriceService.getPrice(name, game);
|
|
priceCache.set(key, price);
|
|
return price;
|
|
}
|
|
```
|
|
|
|
### 4. Handle Missing Prices Gracefully
|
|
|
|
```javascript
|
|
const price = await marketPriceService.getPrice(name, game) || 0.00;
|
|
```
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
### Planned Features
|
|
|
|
- [ ] Price history tracking
|
|
- [ ] Price change alerts
|
|
- [ ] Trend analysis
|
|
- [ ] Auto-update scheduler
|
|
- [ ] Price comparison charts
|
|
- [ ] Volume data
|
|
- [ ] Market depth
|
|
|
|
---
|
|
|
|
## Support
|
|
|
|
**Files**:
|
|
- Model: `models/MarketPrice.js`
|
|
- Service: `services/marketPrice.js`
|
|
- Import Script: `import-market-prices.js`
|
|
|
|
**Related Docs**:
|
|
- `ADMIN_PANEL.md` - Admin price management
|
|
- `PRICING_SYSTEM.md` - Legacy pricing system
|
|
- `API_ENDPOINTS.md` - API documentation
|
|
|
|
---
|
|
|
|
**Last Updated**: January 2025
|
|
**Version**: 1.0.0
|
|
**Status**: ✅ Production Ready (34,641 items) |