first commit
This commit is contained in:
551
MARKET_PRICES.md
Normal file
551
MARKET_PRICES.md
Normal file
@@ -0,0 +1,551 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user