| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- <?php
- namespace App\Module\Mex\Logic;
- use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
- use App\Module\Mex\Dto\MexDailyPriceTrendDto;
- use App\Module\Mex\Models\MexDailyPriceTrend;
- use App\Module\Mex\Models\MexTransaction;
- use Carbon\Carbon;
- use Illuminate\Support\Collection;
- /**
- * 农贸市场每日价格趋势逻辑层
- */
- class MexDailyPriceTrendLogic
- {
- /**
- * 生成指定日期的价格趋势数据
- */
- public function generateDailyTrend(string $date, int $itemId, FUND_CURRENCY_TYPE $currencyType): ?MexDailyPriceTrendDto
- {
- $startDate = Carbon::parse($date)->startOfDay();
- $endDate = Carbon::parse($date)->endOfDay();
- // 获取当日所有成交记录
- $transactions = MexTransaction::where('item_id', $itemId)
- ->where('currency_type', $currencyType)
- ->whereBetween('created_at', [$startDate, $endDate])
- ->orderBy('created_at')
- ->get();
- if ($transactions->isEmpty()) {
- // 没有成交记录时,使用前一日数据或价格配置数据生成趋势记录
- return $this->generateTrendFromPreviousOrConfig($date, $itemId, $currencyType);
- }
- // 计算价格统计
- $priceStats = $this->calculatePriceStatistics($transactions);
- // 计算买入卖出价格统计
- $buyPriceStats = $this->calculateBuyPriceStatistics($transactions);
- $sellPriceStats = $this->calculateSellPriceStatistics($transactions);
- // 计算交易统计
- $tradeStats = $this->calculateTradeStatistics($transactions);
-
- // 获取前一日收盘价用于计算价格变化
- $previousClosePrice = $this->getPreviousClosePrice($date, $itemId, $currencyType);
-
- // 计算价格变化
- $priceChange = $this->calculatePriceChange($priceStats['close_price'], $previousClosePrice);
- // 创建或更新趋势记录
- $trend = MexDailyPriceTrend::updateOrCreate(
- [
- 'item_id' => $itemId,
- 'currency_type' => $currencyType,
- 'trade_date' => $date,
- ],
- array_merge($priceStats, $buyPriceStats, $sellPriceStats, $tradeStats, $priceChange)
- );
- return MexDailyPriceTrendDto::fromModel($trend);
- }
- /**
- * 批量生成多日价格趋势数据
- */
- public function generateMultipleDaysTrends(
- string $startDate,
- string $endDate,
- ?int $itemId = null,
- ?FUND_CURRENCY_TYPE $currencyType = null
- ): Collection {
- $results = collect();
- $current = Carbon::parse($startDate);
- $end = Carbon::parse($endDate);
- while ($current <= $end) {
- $dateStr = $current->format('Y-m-d');
-
- if ($itemId && $currencyType) {
- // 生成指定商品的趋势
- $trend = $this->generateDailyTrend($dateStr, $itemId, $currencyType);
- if ($trend) {
- $results->push($trend);
- }
- } else {
- // 生成所有商品的趋势
- $this->generateAllItemsTrendsForDate($dateStr, $results);
- }
-
- $current->addDay();
- }
- return $results;
- }
- /**
- * 获取商品的价格趋势历史
- */
- public function getItemPriceTrends(
- int $itemId,
- FUND_CURRENCY_TYPE $currencyType,
- string $startDate,
- string $endDate,
- int $limit = 100
- ): Collection {
- $trends = MexDailyPriceTrend::where('item_id', $itemId)
- ->where('currency_type', $currencyType)
- ->whereBetween('trade_date', [$startDate, $endDate])
- ->orderBy('trade_date', 'desc')
- ->limit($limit)
- ->get();
- return $trends->map(fn($trend) => MexDailyPriceTrendDto::fromModel($trend));
- }
- /**
- * 获取价格趋势统计信息
- */
- public function getTrendStatistics(
- int $itemId,
- FUND_CURRENCY_TYPE $currencyType,
- string $startDate,
- string $endDate
- ): array {
- $trends = MexDailyPriceTrend::where('item_id', $itemId)
- ->where('currency_type', $currencyType)
- ->whereBetween('trade_date', [$startDate, $endDate])
- ->get();
- if ($trends->isEmpty()) {
- return [];
- }
- return [
- 'period_start' => $startDate,
- 'period_end' => $endDate,
- 'trading_days' => $trends->count(),
- 'total_volume' => $trends->sum('total_volume'),
- 'total_amount' => $trends->sum('total_amount'),
- 'avg_daily_volume' => $trends->avg('total_volume'),
- 'avg_daily_amount' => $trends->avg('total_amount'),
- 'highest_price' => $trends->max('high_price'),
- 'lowest_price' => $trends->min('low_price'),
- 'period_start_price' => $trends->sortBy('trade_date')->first()->open_price,
- 'period_end_price' => $trends->sortByDesc('trade_date')->first()->close_price,
- 'avg_volatility' => $trends->avg('volatility'),
- 'max_volatility' => $trends->max('volatility'),
- ];
- }
- /**
- * 计算价格统计
- */
- private function calculatePriceStatistics(Collection $transactions): array
- {
- $prices = $transactions->pluck('price');
- // 计算加权平均价格
- $totalAmount = $transactions->sum('total_amount');
- $totalQuantity = $transactions->sum('quantity');
- $avgPrice = $totalQuantity > 0 ? $totalAmount / $totalQuantity : 0;
- $openPrice = $transactions->first()->price;
- $closePrice = $transactions->last()->price;
- $highPrice = $prices->max();
- $lowPrice = $prices->min();
- // 计算波动率
- $volatility = $openPrice > 0 ? (($highPrice - $lowPrice) / $openPrice * 100) : 0;
- return [
- 'open_price' => $openPrice,
- 'close_price' => $closePrice,
- 'high_price' => $highPrice,
- 'low_price' => $lowPrice,
- 'avg_price' => $avgPrice,
- 'volatility' => $volatility,
- ];
- }
- /**
- * 计算交易统计
- */
- private function calculateTradeStatistics(Collection $transactions): array
- {
- $totalVolume = $transactions->sum('quantity');
- $totalAmount = $transactions->sum('total_amount');
- $transactionCount = $transactions->count();
- // 按交易类型分组统计
- $userBuyTransactions = $transactions->where('transaction_type', 'USER_BUY');
- $userSellTransactions = $transactions->where('transaction_type', 'USER_SELL');
- $adminInjectTransactions = $transactions->where('transaction_type', 'ADMIN_INJECT');
- $adminRecycleTransactions = $transactions->where('transaction_type', 'ADMIN_RECYCLE');
- return [
- 'total_volume' => $totalVolume,
- 'total_amount' => $totalAmount,
- 'transaction_count' => $transactionCount,
- 'buy_volume' => $userBuyTransactions->sum('quantity'),
- 'sell_volume' => $userSellTransactions->sum('quantity'),
- 'buy_amount' => $userBuyTransactions->sum('total_amount'),
- 'sell_amount' => $userSellTransactions->sum('total_amount'),
- 'admin_inject_volume' => $adminInjectTransactions->sum('quantity'),
- 'admin_recycle_volume' => $adminRecycleTransactions->sum('quantity'),
- 'admin_inject_amount' => $adminInjectTransactions->sum('total_amount'),
- 'admin_recycle_amount' => $adminRecycleTransactions->sum('total_amount'),
- ];
- }
- /**
- * 获取前一日收盘价
- */
- private function getPreviousClosePrice(string $date, int $itemId, FUND_CURRENCY_TYPE $currencyType): ?float
- {
- $previousDate = Carbon::parse($date)->subDay()->format('Y-m-d');
- $previousTrend = MexDailyPriceTrend::where('item_id', $itemId)
- ->where('currency_type', $currencyType)
- ->where('trade_date', $previousDate)
- ->first();
- return $previousTrend?->close_price;
- }
- /**
- * 计算价格变化
- */
- private function calculatePriceChange(float $currentPrice, ?float $previousPrice): array
- {
- if ($previousPrice === null || $previousPrice == 0) {
- return [
- 'price_change' => null,
- 'price_change_percent' => null,
- ];
- }
- $priceChange = $currentPrice - $previousPrice;
- $priceChangePercent = ($priceChange / $previousPrice) * 100;
- return [
- 'price_change' => $priceChange,
- 'price_change_percent' => $priceChangePercent,
- ];
- }
- /**
- * 生成指定日期所有商品的趋势数据
- */
- private function generateAllItemsTrendsForDate(string $date, Collection &$results): void
- {
- // 获取当日有交易的所有商品和币种组合
- $itemCurrencyPairs = MexTransaction::whereDate('created_at', $date)
- ->select('item_id', 'currency_type')
- ->distinct()
- ->get();
- foreach ($itemCurrencyPairs as $pair) {
- $trend = $this->generateDailyTrend($date, $pair->item_id, $pair->currency_type);
- if ($trend) {
- $results->push($trend);
- }
- }
- }
- /**
- * 从前一日数据或价格配置生成趋势记录
- */
- private function generateTrendFromPreviousOrConfig(string $date, int $itemId, FUND_CURRENCY_TYPE $currencyType): ?MexDailyPriceTrendDto
- {
- // 获取前一日收盘价
- $previousClosePrice = $this->getPreviousClosePrice($date, $itemId, $currencyType);
- if ($previousClosePrice !== null) {
- // 使用前一日收盘价作为当日所有价格
- $priceData = [
- 'open_price' => $previousClosePrice,
- 'close_price' => $previousClosePrice,
- 'high_price' => $previousClosePrice,
- 'low_price' => $previousClosePrice,
- 'avg_price' => $previousClosePrice,
- 'buy_open_price' => $previousClosePrice,
- 'buy_close_price' => $previousClosePrice,
- 'buy_high_price' => $previousClosePrice,
- 'buy_low_price' => $previousClosePrice,
- 'buy_avg_price' => $previousClosePrice,
- 'sell_open_price' => $previousClosePrice,
- 'sell_close_price' => $previousClosePrice,
- 'sell_high_price' => $previousClosePrice,
- 'sell_low_price' => $previousClosePrice,
- 'sell_avg_price' => $previousClosePrice,
- ];
- } else {
- // 使用价格配置数据
- $priceConfig = \App\Module\Mex\Logic\MexPriceConfigLogic::getItemPriceConfig($itemId);
- if (!$priceConfig) {
- return null; // 没有价格配置,无法生成趋势
- }
- // 使用价格配置的中间价作为参考价格
- $referencePrice = ($priceConfig['min_price'] + $priceConfig['max_price']) / 2;
- $priceData = [
- 'open_price' => $referencePrice,
- 'close_price' => $referencePrice,
- 'high_price' => $referencePrice,
- 'low_price' => $referencePrice,
- 'avg_price' => $referencePrice,
- 'buy_open_price' => $referencePrice,
- 'buy_close_price' => $referencePrice,
- 'buy_high_price' => $referencePrice,
- 'buy_low_price' => $referencePrice,
- 'buy_avg_price' => $referencePrice,
- 'sell_open_price' => $referencePrice,
- 'sell_close_price' => $referencePrice,
- 'sell_high_price' => $referencePrice,
- 'sell_low_price' => $referencePrice,
- 'sell_avg_price' => $referencePrice,
- ];
- }
- // 无成交时的统计数据
- $statsData = [
- 'total_volume' => 0,
- 'total_amount' => 0,
- 'transaction_count' => 0,
- 'buy_volume' => 0,
- 'sell_volume' => 0,
- 'buy_amount' => 0,
- 'sell_amount' => 0,
- 'admin_inject_volume' => 0,
- 'admin_recycle_volume' => 0,
- 'admin_inject_amount' => 0,
- 'admin_recycle_amount' => 0,
- 'volatility' => 0,
- ];
- // 计算价格变化
- $priceChange = $this->calculatePriceChange($priceData['close_price'], $previousClosePrice);
- // 创建或更新趋势记录
- $trend = MexDailyPriceTrend::updateOrCreate(
- [
- 'item_id' => $itemId,
- 'currency_type' => $currencyType,
- 'trade_date' => $date,
- ],
- array_merge($priceData, $statsData, $priceChange)
- );
- return MexDailyPriceTrendDto::fromModel($trend);
- }
- /**
- * 计算买入价格统计
- */
- private function calculateBuyPriceStatistics(Collection $transactions): array
- {
- // 筛选买入交易
- $buyTransactions = $transactions->where('transaction_type', 'USER_BUY');
- if ($buyTransactions->isEmpty()) {
- return [
- 'buy_open_price' => null,
- 'buy_close_price' => null,
- 'buy_high_price' => null,
- 'buy_low_price' => null,
- 'buy_avg_price' => null,
- ];
- }
- $buyPrices = $buyTransactions->pluck('price');
- // 计算买入加权平均价格
- $buyTotalAmount = $buyTransactions->sum('total_amount');
- $buyTotalQuantity = $buyTransactions->sum('quantity');
- $buyAvgPrice = $buyTotalQuantity > 0 ? $buyTotalAmount / $buyTotalQuantity : 0;
- return [
- 'buy_open_price' => $buyTransactions->first()->price,
- 'buy_close_price' => $buyTransactions->last()->price,
- 'buy_high_price' => $buyPrices->max(),
- 'buy_low_price' => $buyPrices->min(),
- 'buy_avg_price' => $buyAvgPrice,
- ];
- }
- /**
- * 计算卖出价格统计
- */
- private function calculateSellPriceStatistics(Collection $transactions): array
- {
- // 筛选卖出交易
- $sellTransactions = $transactions->where('transaction_type', 'USER_SELL');
- if ($sellTransactions->isEmpty()) {
- return [
- 'sell_open_price' => null,
- 'sell_close_price' => null,
- 'sell_high_price' => null,
- 'sell_low_price' => null,
- 'sell_avg_price' => null,
- ];
- }
- $sellPrices = $sellTransactions->pluck('price');
- // 计算卖出加权平均价格
- $sellTotalAmount = $sellTransactions->sum('total_amount');
- $sellTotalQuantity = $sellTransactions->sum('quantity');
- $sellAvgPrice = $sellTotalQuantity > 0 ? $sellTotalAmount / $sellTotalQuantity : 0;
- return [
- 'sell_open_price' => $sellTransactions->first()->price,
- 'sell_close_price' => $sellTransactions->last()->price,
- 'sell_high_price' => $sellPrices->max(),
- 'sell_low_price' => $sellPrices->min(),
- 'sell_avg_price' => $sellAvgPrice,
- ];
- }
- }
|