If you would have created your own WP_Query, you could have done it this way:
<?php
$args = array(
'orderby' => 'meta_value_num',
'order' => 'ASC',
'meta_key' => 'price'
);
$new_query = new WP_Query( $args );
?>
See the examples in the docs:
https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters
Since you are using the Mainloop, you need to act a bit differently:
<?php
add_action( 'pre_get_posts', 'order_by_price' );
function order_by_price( $query ){
if( ! $query->is_main_query() ) //If its not the main query return
return;
$query->set( 'order', 'asc' );
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_key', 'price' );
}
?>
This will act on each Mainloop. So be careful. Maybe you want this to be applied only for specific post types. In this case, you should check this first:
<?php
add_action( 'pre_get_posts', 'order_by_price' );
function order_by_price( $query ){
if( ! $query->is_main_query() ) //If its not the main query return
return;
if( 'product' != $query->get( 'post_type' ) ) //Apply only for 'product' post types
return;
$query->set( 'order', 'asc' );
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_key', 'price' );
}
?>
Check also the Docs for this Actionhook:
https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts