You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Livewire 大批量交易数据分析计算加载缓慢优化咨询

性能优化方案

1. 数据库查询层优化(收益最高,优先改)

  • 把「先查全量交易再用集合过滤时间」的逻辑改成「先加时间过滤条件再查询」,直接减少返回的数据集大小,选7D/1M这类短周期时数据量会下降90%以上。
  • 给trades表添加联合索引idx_portfolio_entry_status(portfolio_id, entry_date, status),所有查询都会走索引,查询速度提升10倍以上。
  • 简单聚合运算直接用SQL完成,不要拉到PHP层计算,比如总和、计数、最大最小值可以直接用sum()、count()、max()、min() SQL函数处理。

filterTrades修改示例:

public function filterTrades()
{
    $query = $this->portfolio->trades(['return', 'return_percentage', 'status', 'entry_date', 'id'])
        ->orderBy('entry_date', 'desc');

    // 时间条件先加在查询构造器上,再执行查询
    switch ($this->filter) {
        case '7D':
            $query->whereBetween('entry_date', [now()->startOfWeek(), now()->endOfWeek()]);
            break;
        case '1M':
            $query->whereBetween('entry_date', [now()->startOfMonth(), now()->endOfMonth()]);
            break;
        case '3M':
            $query->whereBetween('entry_date', [now()->startOfMonth()->subMonths(2), now()->endOfMonth()]);
            break;
        case '6M':
            $query->whereBetween('entry_date', [now()->startOfMonth()->subMonths(5), now()->endOfMonth()]);
            break;
        case '1Y':
            $query->whereBetween('entry_date', [now()->startOfYear(), now()->endOfYear()]);
            break;
        case '2Y':
            $query->whereBetween('entry_date', [now()->startOfYear()->subYears(1), now()->endOfYear()]);
            break;
        case '3Y':
            $query->whereBetween('entry_date', [now()->startOfYear()->subYears(2), now()->endOfYear()]);
            break;
    }
    return $query->get();
}

2. 计算逻辑优化(减少重复遍历,效率提升数倍)

你当前的Service里存在大量重复的集合遍历:每算一个指标就要遍历一次所有交易,1万条数据的场景下等于要遍历十几次,改成单次遍历计算所有基础指标,后续直接返回预计算的结果即可。

修改后的TradeAnalyticsService示例:

class TradeAnalyticsService
{
    private $trades;
    private $balance = 0;
    // 预计算基础指标
    private $winSum = 0;
    private $loseSum = 0;
    private $winCount = 0;
    private $loseCount = 0;
    private $bestTrade;
    private $worstTrade;
    private $longestWinStreak = 0;
    private $longestLoseStreak = 0;

    public function __construct($trades, int $balance)
    {
        $this->trades = $trades;
        $this->balance = $balance;
        $this->calculateBaseMetrics(); // 构造时仅遍历一次算完所有基础指标
    }

    // 单次遍历计算所有基础指标
    private function calculateBaseMetrics()
    {
        $currentWinStreak = 0;
        $currentLoseStreak = 0;
        $maxReturn = -PHP_INT_MAX;
        $minReturn = PHP_INT_MAX;

        foreach ($this->trades as $trade) {
            if ($trade->status === 'win') {
                $this->winSum += $trade->return;
                $this->winCount++;
                // 连胜计算
                $currentWinStreak++;
                $currentLoseStreak = 0;
                $this->longestWinStreak = max($this->longestWinStreak, $currentWinStreak);
                // 最高收益交易计算
                if ($trade->return > $maxReturn) {
                    $maxReturn = $trade->return;
                    $this->bestTrade = $trade;
                }
            } elseif ($trade->status === 'lose') {
                $this->loseSum += $trade->return;
                $this->loseCount++;
                // 连败计算
                $currentLoseStreak++;
                $currentWinStreak = 0;
                $this->longestLoseStreak = max($this->longestLoseStreak, $currentLoseStreak);
                // 最低收益交易计算
                if ($trade->return < $minReturn) {
                    $minReturn = $trade->return;
                    $this->worstTrade = $trade;
                }
            }
        }
    }

    // 后续所有get方法直接返回预计算结果,无需再次遍历
    public function getProfitFactor()
    {
        $loseAbs = abs($this->loseSum);
        return ($this->winSum < 1 || $loseAbs < 1) ? 0 : $this->winSum / $loseAbs;
    }

    public function getPercentProfitable()
    {
        $total = $this->winCount + $this->loseCount;
        return ($this->winCount < 1 || $total < 1) ? 0 : $this->winCount / $total * 100;
    }

    public function getAverageTradeNetProfit()
    {
        $total = $this->winCount + $this->loseCount;
        return $total < 1 ? 0 : ($this->winSum + $this->loseSum) / $total;
    }

    public function getAverageWinner()
    {
        return $this->winCount < 1 ? 0 : $this->winSum / $this->winCount;
    }

    public function getAverageLoser()
    {
        return $this->loseCount < 1 ? 0 : $this->loseSum / $this->loseCount;
    }

    public function getBestTradeReturn()
    {
        return $this->bestTrade;
    }

    public function getWorstTradeReturn()
    {
        return $this->worstTrade;
    }

    public function getWinCount()
    {
        return $this->winCount;
    }

    public function getLoseCount()
    {
        return $this->loseCount;
    }

    public function getTradeCount()
    {
        return $this->trades->count();
    }

    public function getNetProfit()
    {
        return $this->winSum + $this->loseSum;
    }

    public function getLongestWinStreaks()
    {
        return $this->longestWinStreak;
    }

    public function getLongestLoseStreaks()
    {
        return $this->longestLoseStreak;
    }

    // 其他方法保持原有逻辑即可
}

3. 缓存优化(降低重复计算开销)

  • 给每个组合+筛选维度的计算结果加缓存,缓存Key规则可以设为analytics:portfolio:{组合ID}:filter:{筛选维度},缓存有效期设为5分钟即可。
  • 当组合下有新增/修改/删除交易时,主动清空该组合对应的所有分析缓存,保证数据准确性。
  • 如果仪表盘实时性要求不高,可以提前预计算天、周、月维度的统计数据,存在单独的统计表中,查询时直接读预计算结果,速度可以达到毫秒级。

4. 额外优化点

  • 取消不必要的字段查询:如果后续逻辑不需要instrument字段,就不要在查询时select该字段,减少数据传输开销。
  • 收益趋势的分组求和可以直接在SQL层面完成,用DATE_FORMAT(entry_date, "%d/%m/%y")分组并sum(return),无需拉到PHP层做分组计算。

内容的提问来源于stack exchange,提问作者Sherwin Variancia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 16:06:02