Ok, but you have to realize that this has nothing to do with haproxy. Haproxy creates a socket, and uses that socket. If you configure haproxy with a specific source IP, haproxy will tell the kernel to use that source IP. All the complexity of source IP selection is in the kernel.
If you configured different routing tables, please provide the full output of those.
The strace output I have with your config is the following:
18:32:35.577932 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 14
18:32:35.578074 fcntl(14, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
18:32:35.578188 setsockopt(14, SOL_TCP, TCP_NODELAY, [1], 4) = 0
18:32:35.578326 setsockopt(14, SOL_IP, IP_BIND_ADDRESS_NO_PORT, [1], 4) = 0
18:32:35.578474 setsockopt(14, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
18:32:35.578603 bind(14, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.2")}, 16) = 0
18:32:35.578726 connect(14, {sa_family=AF_INET, sin_port=htons(999), sin_addr=inet_addr("192.168.0.3")}, 16) = -1 EINPROGRESS (Operation now in progress)
18:32:35.579033 sendto(14, "GET / HTTP/1.1\r\nHost: 192.168.0."..., 79, MSG_DONTWAIT|MSG_NOSIGNAL, NULL, 0) = -1 ECONNREFUSED (Connection refused)
18:32:35.579237 setsockopt(14, SOL_SOCKET, SO_LINGER, {onoff=1, linger=0}, 8) = 0
18:32:35.579414 close(14) = 0
You can see the bind call where the socket is bound to the source IP 192.168.0.2 (and the syscall returns success).