A solução é fazer com que o shell substitua as variáveis de cor ao definir o prompt, mas não as funções. Para fazer isso, use aspas duplas como tinha tentado originalmente, mas escape dos comandos para que eles não sejam avaliados até que o prompt seja desenhado.
PS1="\u@\h:\w${YELLOW}\$(virtual_env)${GREEN}\$(git_branch)${RESET}$ "
Observe o \
antes do $()
em cada comando.
Se fizermos eco disso, vemos:
echo "$PS1"
\u@\h:\w\[3[33m\]$(virtual_env)\[3[32m\]$(git_branch)\[3[0m\]$
Como você pode ver, as variáveis de cor foram substituídas, mas não os comandos.