There is a method for finding strings with maximum or minimum values, which includes a LEFT JOIN for itself, and not a more intuitive, but probably more expensive, INNER JOIN for an aggregated list with self-healing.
Basically the method uses this template:
SELECT t.* FROM t LEFT JOIN t AS t2 ON t.key = t2.key AND t2.Value > t.Value WHERE t2.key IS NULL
or its NOT EXISTS:
SELECT * FROM t WHERE NOT EXISTS ( SELECT * FROM t AS t2 WHERE t.key = t2.key AND t2.Value > t.Value )
So, the result is all rows for which there is no row with the same key and value greater than the given.
When there is only one table, applying the above method is quite simple. However, it may not be so obvious how to apply it when there is another table, especially when, as in your case, another table makes a complex query more difficult not only because it exists, but also provides us with additional filtering for the values that we are looking for, namely with upper limits for dates.
So, here is what the resulting query might look like when using the LEFT JOIN version of the method:
SELECT d.TransactionID, d.FundCode, d.TransactionDate, v.OfferPrice FROM Transaction d INNER JOIN Price v ON v.FundCode = d.FundCode LEFT JOIN Price v2 ON v2.FundCode = v.FundCode AND v2.PriceDate > v.PriceDate AND v2.PriceDate < d.TransactionDate WHERE v2.FundCode IS NULL
And here is a similar solution with NOT EXISTS:
SELECT d.TransactionID, d.FundCode, d.TransactionDate, v.OfferPrice FROM Transaction d INNER JOIN Price v ON v.FundCode = d.FundCode WHERE NOT EXISTS ( SELECT * FROM Price v2 WHERE v2.FundCode = v.FundCode AND v2.PriceDate > v.PriceDate AND v2.PriceDate < d.TransactionDate )