In C, memory is either dynamically or statically allocated.
Something like int fifty_numbers[50] is statically allocated. The size is 50 integers no matter what, so the compiler knows how big the array is in bytes. sizeof(fifty_numbers) will give you 200 bytes here.
Dynamic allocation: int *bunch_of_numbers = malloc(sizeof(int) * varying_size). As you can see, varying_size is not constant, so the compiler can't figure out how big the array is without executing the program. sizeof(bunch_of_numbers) gives you 4 bytes on a 32 bit system, or 8 bytes on a 64 bit system. The only one that know how big the array is would be the programmer. In your case, it's whoever wrote do_something_with_rates(), but you're discarding that information by either not returning it, or taking a size parameter.
It's not clear how do_something_with_rates() was declared exactly, but something like: void do_something_with_rates(Rate **rates) won't work as the function has no idea how big rates is. I recommend something like: void do_something_with_rates(size_t array_size, Rate **rates). At any rate, going by your requirements, it's still a ways away from working. Possible solutions are below:
You need to either return the new array's size:
size_t do_something_with_rates(size_t old_array_size, Rate **rates) {
    Rate **new_rates;
    *new_rates = malloc(sizeof(Rate) * n); // allocate n Rate objects
    // carry out your operation on new_rates
    // modifying rates
    free(*rates); // releasing the memory taken up by the old array
    *rates = *new_rates // make it point to the new array
    return n; // returning the new size so that the caller knows
}
int main() {
    Rate *rates = malloc(sizeof(Rate) * 20);
    size_t new_size = do_something_with_rates(20, &rates);
    // now new_size holds the size of the new array, which may or may not be 20
    return 0;
}
Or pass in a size parameter for the function to set:
void do_something_with_rates(size_t old_array_size, size_t *new_array_size, Rate **rates) {
    Rate **new_rates;
    *new_rates = malloc(sizeof(Rate) * n); // allocate n Rate objects
    *new_array_size = n; // setting the new size so that the caller knows
    // carry out your operation on new_rates
    // modifying rates
    free(*rates); // releasing the memory taken up by the old array
    *rates = *new_rates // make it point to the new array
}
int main() {
    Rate *rates = malloc(sizeof(Rate) * 20);
    size_t new_size;
    do_something_with_rates(20, &new_size, &rates);
    // now new_size holds the size of the new array, which may or may not be 20
    return 0;
}
Why do I need to pass the old size as a parameter?
void do_something_with_rates(Rate **rates) {
    // You don't know what n is. How would you
    // know how many rate objects the caller wants
    // you to process for any given call to this?
    for (size_t i = 0; i < n; ++i)
        // carry out your operation on new_rates
}
Everything changes when you have a size parameter:
void do_something_with_rates(size_t size, Rate **rates) {
    for (size_t i = 0; i < size; ++i) // Now you know when to stop
        // carry out your operation on new_rates
}
This is a very fundamental flaw with your program.
I want to also want the function to change the contents of the array:
size_t do_something_with_rates(size_t old_array_size, Rate **rates) {
    Rate **new_rates;
    *new_rates = malloc(sizeof(Rate) * n); // allocate n Rate objects
    // carry out some operation on new_rates
    Rate *array = *new_rates;
    for (size_t i = 0; i < n; ++i) {
        array[i]->timestamp = time();
        // you can see the pattern
    }
    return n; // returning the new size so that the caller knows
}