The reason why the efficient frontier doesn't look good is because It's not the efficient frontier. It's the feasible set.
What your code is doing is generating random portfolios, and plugging these random weight vectors as inputs into the formula of the mean-variance model. These random portfolios, when transformed like this to mean-variance coordinates, only provide the feasible set, which are portfolios inferior to the efficient frontier seen as everything in the cloud to the right of the left boundary, and possibly many portfolios very close to or along the efficient frontier by mere chance if you generate a high enough number of them, but it is not the direct way to solve for the efficient frontier, separate from the feasible set.
In order to plot the efficient frontier, you have solve for optimal portfolios along the efficient frontier directly, which requires convex optimization on the mean-variance model's formula,
$$\min_w w^\top \Sigma w \enspace \text{s.t.} \hspace{2cm} w^\top \mu = \theta, \enspace \sum_{i=1}^N w_i=1 $$
Optimizing the above formula with an optimizer for varying levels of target return $\theta$ will result in an optimal weight vector as an output. As you can tell, this is not the same as plugging in random weights as inputs into the same formula as is done in the random portfolios approach.
In summary, you are currently doing the first of the following two approaches, which explains the high number of portfolios appearing in the far right, creating a circular cloud, being the feasible set, at such a high asset number size (48).
Random portfolios:
- use randomly generated weights as inputs for the mean-variance formula
- All solutions are only feasible portfolios
Optimized portfolios:
- use mean-variance model itself to solve for optimal weights as outputs
- All solutions are efficient portfolios