← back to Wine Finder Next
src/hooks/useArbitrageOpportunities.ts
81 lines
/**
* useArbitrageOpportunities Hook
* Calculates arbitrage opportunities from wine listings
*/
import { useMemo } from 'react';
import { WineListing, ArbitrageOpportunity } from '../types/wine';
interface UseArbitrageOpportunitiesReturn {
opportunities: ArbitrageOpportunity[];
bestDeal: ArbitrageOpportunity | null;
totalProfit: number;
opportunityCount: number;
}
export function useArbitrageOpportunities(
listings: WineListing[]
): UseArbitrageOpportunitiesReturn {
const opportunities = useMemo(() => {
const retail = listings.filter((l) => l.provider === 'retail');
const auction = listings.filter((l) => l.provider === 'auction');
const opps: ArbitrageOpportunity[] = [];
auction.forEach((auctionListing) => {
retail.forEach((retailListing) => {
// Fuzzy match wine names
const nameMatch =
auctionListing.name.toLowerCase().includes(retailListing.name.toLowerCase()) ||
retailListing.name.toLowerCase().includes(auctionListing.name.toLowerCase());
// Vintage match (if both have vintage)
const vintageMatch =
!auctionListing.vintage ||
!retailListing.vintage ||
auctionListing.vintage === retailListing.vintage;
if (nameMatch && vintageMatch) {
const spread = auctionListing.currentPrice.amount - retailListing.currentPrice.amount;
const spreadPercentage = (spread / retailListing.currentPrice.amount) * 100;
// Only show significant arbitrage (>$10)
if (spread > 10) {
opps.push({
wine: {
id: retailListing.id,
name: retailListing.name,
vintage: retailListing.vintage,
region: retailListing.region,
rating: retailListing.rating,
},
retailListing,
auctionListing,
spread,
spreadPercentage,
});
}
}
});
});
// Sort by dollar profit (highest first)
return opps.sort((a, b) => b.spread - a.spread);
}, [listings]);
const bestDeal = useMemo(() => {
return opportunities.length > 0 ? opportunities[0] : null;
}, [opportunities]);
const totalProfit = useMemo(() => {
return opportunities.reduce((sum, opp) => sum + opp.spread, 0);
}, [opportunities]);
return {
opportunities,
bestDeal,
totalProfit,
opportunityCount: opportunities.length,
};
}