Your query seems to be in order except for the fact that you do need to reset your query once done. Simply just add
endif. Failing to do so might lead to other queries being influenced by this query. The opposite is also true, other custom queries can also influence this one if not resetted.
I can only speculate to what might be wrong, but here is a couple of point to inspect
post_type -> By default, except for tax queries, this parameter is set to
post. If you are using a custom post type or pages, your query will simply return nothing
post_status -> By default this is set to
private is added when the user is logged in. If your post's status is anything else, you will need to add that to your query.
If this does not work, inspect your SQL request by placing the following code just below your query
and then taking it from there.
One quick tip, if you want to "safegaurd" a new instance of
WP_Query against modifications from filters, just add
'suppress_filters' => true, to your query arguments. This is also useful in debugging as this can be a quick test to see if unexpexted behavior by a custom query is caused by a custom filter
On your shortcode, before you even create one, just a tip, if you are going to use
do_shortcode() in a template file, then you should reconsider and just use the function and drop the shortcode. Shortcodes need to be parsed, so it will be slower than just calling the function
If you do see the use of a shortcode fit for your needs, you need adjust your custom query accordingly. Shortcodes should never echo their output, but return it instead. To do this, use the
get_ prefix functions of template tags you are currently using, then add your HTML markup and template tags to a variable and simply return the variable at the end