Я думаю, что здесь требуется условное агрегирование:
SELECT
i.descricao as cotacao,
m.quantidade as qtdComprada,
m.valor as vlrUnitario,
(m.valor * m.quantidade) as valorCompra,
MAX(CASE WHEN v.hora = '08:00' THEN v.valor END) as vlrAberto,
MAX(CASE WHEN v.hora = '18:00' THEN v.valor END) as vlrFechado,
v.data as data
FROM
movimento_indices m
LEFT JOIN indices i ON i.idindice = m.idindice
LEFT JOIN valores_indices v ON v.idindice = m.idindice
WHERE v.hora IN ('08:00', '18:00')
GROUP BY
i.descricao,
m.quantidade,
m.valor,
m.quantidade,
v.data
Другой вариант - боковое соединение:
SELECT
i.descricao as cotacao,
m.quantidade as qtdComprada,
m.valor as vlrUnitario,
(m.valor * m.quantidade) as valorCompra,
v.vlrAberto,
v.vlrFechado,
v.data as data
FROM
movimento_indices m
LEFT JOIN indices i ON i.idindice = m.idindice
LEFT LATERAL JOIN (
SELECT
MAX(v.valor) FILTER(WHERE v.hora = '08:00') as vlrAberto,
MAX(v.valor) FILTER(WHERE v.hora = '18:00') as vlrFechado,
v.data
FROM valores_indices v
WHERE v.idindice = m.idindice AND v.hora IN ('08:00', '18:00')
GROUP BY v.data
) v ON 1 = 1