AI-Driven FOREX Experimentation Framework
Even at an early stage in the project, the complexity has risen to the point that bugs cause side effects. To mitigate this, tests are being added to this project.
Most tests here are pytest tests. They can be executed directly by providing the path to a test file or with poetry run pytest -v to run all tests. Execute this command from the base project directory.
For the local project virtual environment, use:
aifx_venv/bin/python -m pytest tests/unit
Current unit coverage is 144 tests across tests/unit.
3rd party testing tools have also been integrated into this project:
Configuration:
- (aifx_venv) dan@sally:/opt/dev/aifx$ cat .flake8
[flake8]
max-line-length = 88
extend-ignore = E203
exclude =
.git,
__pycache__,
.venv,
aifx/ui_form.py,
aifx/client/ui_form.py
Configuration (from pyproject.toml):
[[tool.mypy.overrides]]
module = ["plotly", "plotly.*"]
ignore_missing_imports = true
test_aifxlog_uses_module_name_as_logger_client_id()test_aifxlog_sets_configured_log_level()test_aifxlog_adds_one_console_handler()test_aifxlog_does_not_duplicate_console_handlers()test_aifxlog_adds_one_file_handler()test_aifxlog_does_not_duplicate_file_handlers()test_loglevel_changes_logger_level()test_invalid_log_level_raises_key_error()test_logging_methods_accept_normal_messages()sample_candle()sample_instrument()db_mgr()test_get_instruments_returns_none_when_empty()test_get_instruments_returns_instruments_sorted_by_name()test_get_latest_candle_returns_none_when_empty()test_get_latest_candle_returns_newest_candle_for_instrument()test_get_recent_candles_returns_empty_list_when_empty()test_get_recent_candles_filters_by_instrument_and_returns_oldest_first()test_get_instruments_returns_cached_db_instruments()test_get_instruments_fetches_oanda_when_cache_is_empty()test_get_instruments_returns_empty_dict_when_no_data()test_broker_registers_shutdown_method()test_get_recent_candles_returns_cached_db_candles()test_get_recent_candles_fetches_oanda_when_cache_is_empty()test_get_recent_candles_converts_oanda_payload_to_reply_format()test_get_recent_candles_returns_empty_list_when_no_data()test_publish_oanda_status_schedules_mq_publish()test_start_feed_creates_feed_and_background_tasks()test_start_feed_is_idempotent_for_existing_feed()test_shutdown_returns_ack_and_schedules_quit()test_quit_cancels_tasks_stops_mq_and_closes_db()test_quit_is_idempotent()test_dbmgr_rejects_unknown_db_type()test_new_cache_db_has_empty_tables()test_select_one_returns_none_when_no_rows()test_select_all_returns_empty_list_when_no_rows()test_upsert_empty_records_returns_zero()test_upsert_instrument_inserts_row()test_upsert_instrument_updates_existing_row()test_is_stale_returns_true_for_empty_instruments_table()test_is_stale_rejects_table_without_stale_config()test_add_latency_upserts_row()test_candle_key_is_sortable_timestamp_string()test_to_dict_does_not_include_candle_key()test_from_db_round_trips_from_to_dict()test_from_oanda_builds_candle()test_clientdb_get_recent_candles_returns_empty_list_when_empty()test_clientdb_upsert_candles_accepts_candle_objects()test_clientdb_upsert_candles_accepts_dict_records()test_clientdb_get_recent_candles_filters_limits_and_returns_oldest_first()test_clientdb_upsert_candles_updates_existing_row()test_clientdb_upsert_instruments_accepts_instrument_objects()test_clientdb_upsert_instruments_accepts_dict_records()test_on_instrument_changed_renders_from_client_cache()test_on_instrument_changed_requests_broker_when_client_cache_is_empty()test_on_recent_candles_upserts_and_renders_from_client_cache()test_on_candle_received_upserts_and_renders_from_client_cache()test_start_mq_subscribes_to_oanda_latency_topic()test_on_oanda_latency_received_records_latency_and_updates_plot()test_set_connection_status_records_broker_latency()test_set_connection_status_requests_instruments_on_first_connect()test_set_connection_status_marks_disconnected_without_ui_labels()test_latency_plot_html_configures_title_legend_and_current_latency()test_update_latency_plot_uses_latest_points_in_time_order()test_to_dict()test_from_db_round_trip()test_from_oanda()test_feed_stores_name_and_defaults_runtime_state()test_feed_runtime_state_can_be_updated()test_feed_supports_dataclass_equality_for_same_state()test_mqevent_stores_event_type_and_defaults_optional_fields()test_mqevent_stores_routing_id_client_id_and_payload()test_mqevent_supports_dataclass_equality()test_mqevent_is_frozen()test_mqclient_initializes_sockets_and_addresses()test_mqclient_builds_topics()test_mqclient_connected_uses_recent_heartbeat()test_mqclient_heartbeat_reply_emits_broker_status_with_latency()test_mqclient_does_not_send_overlapping_heartbeats()test_mqclient_register_subscribe_and_unsubscribe()test_mqclient_send_serializes_message()test_mqclient_send_returns_false_when_socket_would_block()test_mqclient_get_instruments_sends_request()test_mqclient_get_recent_candles_sends_request()test_mqclient_start_feed_sends_start_feed_message()test_mqclient_poll_control_reply_emits_instruments()test_mqclient_poll_control_reply_emits_recent_candles()test_mqclient_bg_sub_listen_dispatches_registered_handler()test_mqclient_quit_closes_sockets_and_context()test_mqmsg_stores_fields_and_defaults_payload_to_empty_dict()test_mqmsg_properties_can_be_updated()test_to_dict_includes_protocol_version()test_to_json_returns_utf8_json_bytes()test_from_dict_reconstructs_message()test_from_dict_defaults_missing_target_to_none()test_from_dict_defaults_missing_or_none_payload_to_empty_dict()test_from_dict_rejects_non_dict_payload()test_from_json_round_trips_from_to_json()test_mqserver_initializes_addresses_and_sockets()test_mqserver_builds_topic()test_mqserver_connected_uses_recent_heartbeat()test_mqserver_publish_sends_topic_and_compact_json()test_mqserver_wraps_recent_candles_handler_result_in_reply()test_mqserver_send_serializes_message()test_mqserver_recv_parses_message()test_mqserver_register_client_adds_new_client_and_event()test_mqserver_register_existing_client_updates_without_event()test_mqserver_remove_client_removes_client_and_event()test_mqserver_quit_returns_when_not_started()test_new_batch_starts_empty()test_append_requires_lock()test_pop_batch_requires_lock()test_append_tracks_size_and_timeout()test_pop_batch_returns_messages_resets_state_and_increments_batch_num()test_batch_can_be_reused_after_pop()test_encode_json_encodes_string_as_utf8_bytes()test_decode_json_decodes_bytes_to_dict()test_decode_json_decodes_zmq_frame_to_dict()test_ensure_bytes_returns_bytes_unchanged()test_ensure_bytes_returns_frame_bytes()test_split_router_frames_without_empty_delimiter()test_split_router_frames_with_empty_delimiter()test_split_router_frames_rejects_too_few_frames()test_ignore_zmq_teardown_suppresses_zmq_error()test_ignore_zmq_teardown_does_not_suppress_other_errors()test_fetch_instruments_returns_status_and_data_on_http_200()test_fetch_instruments_returns_none_tuple_on_non_200()test_fetch_instruments_returns_none_tuple_on_request_exception()test_get_instruments_returns_none_when_fetch_fails()test_get_instruments_converts_oanda_payloads_to_instruments()test_fetch_candles_passes_request_details_and_returns_response()test_get_candles_returns_none_on_non_200()test_get_candles_converts_only_complete_candles()test_recent_candles_model_starts_empty()test_recent_candles_model_loads_candles()test_recent_candles_model_horizontal_headers()test_recent_candles_model_vertical_headers_are_one_based()test_recent_candles_model_aligns_headers_right()test_recent_candles_model_ignores_unhandled_header_roles()test_recent_candles_model_formats_display_values()test_recent_candles_model_aligns_display_values_right()test_recent_candles_model_colors_bullish_candles_green()test_recent_candles_model_colors_bearish_candles_red()test_recent_candles_model_leaves_neutral_candles_uncolored()test_recent_candles_model_returns_none_for_invalid_index()test_recent_candles_model_returns_none_for_unhandled_data_role()test_recent_candles_model_clear_removes_rows()