diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 604f1553..00000000
--- a/.editorconfig
+++ /dev/null
@@ -1,721 +0,0 @@
-[*]
-charset = utf-8
-end_of_line = lf
-indent_size = 4
-indent_style = space
-insert_final_newline = true
-max_line_length = 120
-tab_width = 4
-ij_continuation_indent_size = 8
-ij_formatter_off_tag = @formatter:off
-ij_formatter_on_tag = @formatter:on
-ij_formatter_tags_enabled = true
-ij_smart_tabs = false
-ij_wrap_on_typing = false
-
-[*.java]
-ij_java_align_consecutive_assignments = false
-ij_java_align_consecutive_variable_declarations = false
-ij_java_align_group_field_declarations = false
-ij_java_align_multiline_annotation_parameters = false
-ij_java_align_multiline_array_initializer_expression = false
-ij_java_align_multiline_assignment = false
-ij_java_align_multiline_binary_operation = false
-ij_java_align_multiline_chained_methods = false
-ij_java_align_multiline_extends_list = false
-ij_java_align_multiline_for = false
-ij_java_align_multiline_method_parentheses = false
-ij_java_align_multiline_parameters = false
-ij_java_align_multiline_parameters_in_calls = false
-ij_java_align_multiline_parenthesized_expression = false
-ij_java_align_multiline_resources = false
-ij_java_align_multiline_ternary_operation = false
-ij_java_align_multiline_throws_list = false
-ij_java_align_subsequent_simple_methods = false
-ij_java_align_throws_keyword = false
-ij_java_annotation_parameter_wrap = normal
-ij_java_array_initializer_new_line_after_left_brace = false
-ij_java_array_initializer_right_brace_on_new_line = false
-ij_java_array_initializer_wrap = normal
-ij_java_assert_statement_colon_on_next_line = false
-ij_java_assert_statement_wrap = normal
-ij_java_assignment_wrap = normal
-ij_java_binary_operation_sign_on_next_line = false
-ij_java_binary_operation_wrap = normal
-ij_java_blank_lines_after_anonymous_class_header = 0
-ij_java_blank_lines_after_class_header = 1
-ij_java_blank_lines_after_imports = 1
-ij_java_blank_lines_after_package = 1
-ij_java_blank_lines_around_class = 1
-ij_java_blank_lines_around_field = 0
-ij_java_blank_lines_around_field_in_interface = 0
-ij_java_blank_lines_around_initializer = 1
-ij_java_blank_lines_around_method = 1
-ij_java_blank_lines_around_method_in_interface = 1
-ij_java_blank_lines_before_class_end = 0
-ij_java_blank_lines_before_imports = 1
-ij_java_blank_lines_before_method_body = 0
-ij_java_blank_lines_before_package = 0
-ij_java_block_brace_style = end_of_line
-ij_java_block_comment_at_first_column = true
-ij_java_call_parameters_new_line_after_left_paren = false
-ij_java_call_parameters_right_paren_on_new_line = false
-ij_java_call_parameters_wrap = normal
-ij_java_case_statement_on_separate_line = true
-ij_java_catch_on_new_line = false
-ij_java_class_annotation_wrap = split_into_lines
-ij_java_class_brace_style = end_of_line
-ij_java_class_count_to_use_import_on_demand = 20
-ij_java_class_names_in_javadoc = 1
-ij_java_do_not_indent_top_level_class_members = false
-ij_java_do_not_wrap_after_single_annotation = false
-ij_java_do_while_brace_force = always
-ij_java_doc_add_blank_line_after_description = true
-ij_java_doc_add_blank_line_after_param_comments = true
-ij_java_doc_add_blank_line_after_return = true
-ij_java_doc_add_p_tag_on_empty_lines = true
-ij_java_doc_align_exception_comments = true
-ij_java_doc_align_param_comments = true
-ij_java_doc_do_not_wrap_if_one_line = true
-ij_java_doc_enable_formatting = true
-ij_java_doc_enable_leading_asterisks = true
-ij_java_doc_indent_on_continuation = false
-ij_java_doc_keep_empty_lines = true
-ij_java_doc_keep_empty_parameter_tag = true
-ij_java_doc_keep_empty_return_tag = true
-ij_java_doc_keep_empty_throws_tag = true
-ij_java_doc_keep_invalid_tags = true
-ij_java_doc_param_description_on_new_line = false
-ij_java_doc_preserve_line_breaks = false
-ij_java_doc_use_throws_not_exception_tag = true
-ij_java_else_on_new_line = false
-ij_java_enum_constants_wrap = normal
-ij_java_extends_keyword_wrap = normal
-ij_java_extends_list_wrap = normal
-ij_java_field_annotation_wrap = split_into_lines
-ij_java_finally_on_new_line = false
-ij_java_for_brace_force = always
-ij_java_for_statement_new_line_after_left_paren = false
-ij_java_for_statement_right_paren_on_new_line = false
-ij_java_for_statement_wrap = normal
-ij_java_generate_final_locals = false
-ij_java_generate_final_parameters = false
-ij_java_if_brace_force = always
-ij_java_imports_layout = *,|,javax.**,java.**,|,$*
-ij_java_indent_case_from_switch = true
-ij_java_insert_inner_class_imports = false
-ij_java_insert_override_annotation = true
-ij_java_keep_blank_lines_before_right_brace = 1
-ij_java_keep_blank_lines_between_package_declaration_and_header = 1
-ij_java_keep_blank_lines_in_code = 1
-ij_java_keep_blank_lines_in_declarations = 1
-ij_java_keep_control_statement_in_one_line = true
-ij_java_keep_first_column_comment = true
-ij_java_keep_indents_on_empty_lines = false
-ij_java_keep_line_breaks = true
-ij_java_keep_multiple_expressions_in_one_line = true
-ij_java_keep_simple_blocks_in_one_line = true
-ij_java_keep_simple_classes_in_one_line = true
-ij_java_keep_simple_lambdas_in_one_line = true
-ij_java_keep_simple_methods_in_one_line = true
-ij_java_lambda_brace_style = end_of_line
-ij_java_layout_static_imports_separately = true
-ij_java_line_comment_add_space = true
-ij_java_line_comment_at_first_column = false
-ij_java_method_annotation_wrap = split_into_lines
-ij_java_method_brace_style = end_of_line
-ij_java_method_call_chain_wrap = normal
-ij_java_method_parameters_new_line_after_left_paren = false
-ij_java_method_parameters_right_paren_on_new_line = false
-ij_java_method_parameters_wrap = normal
-ij_java_modifier_list_wrap = false
-ij_java_names_count_to_use_import_on_demand = 20
-ij_java_parameter_annotation_wrap = normal
-ij_java_parentheses_expression_new_line_after_left_paren = false
-ij_java_parentheses_expression_right_paren_on_new_line = false
-ij_java_place_assignment_sign_on_next_line = false
-ij_java_prefer_longer_names = true
-ij_java_prefer_parameters_wrap = false
-ij_java_repeat_synchronized = true
-ij_java_replace_instanceof_and_cast = false
-ij_java_replace_null_check = true
-ij_java_replace_sum_lambda_with_method_ref = true
-ij_java_resource_list_new_line_after_left_paren = false
-ij_java_resource_list_right_paren_on_new_line = false
-ij_java_resource_list_wrap = normal
-ij_java_space_after_closing_angle_bracket_in_type_argument = false
-ij_java_space_after_colon = true
-ij_java_space_after_comma = true
-ij_java_space_after_comma_in_type_arguments = true
-ij_java_space_after_for_semicolon = true
-ij_java_space_after_quest = true
-ij_java_space_after_type_cast = true
-ij_java_space_before_annotation_array_initializer_left_brace = false
-ij_java_space_before_annotation_parameter_list = false
-ij_java_space_before_array_initializer_left_brace = false
-ij_java_space_before_catch_keyword = true
-ij_java_space_before_catch_left_brace = true
-ij_java_space_before_catch_parentheses = true
-ij_java_space_before_class_left_brace = true
-ij_java_space_before_colon = true
-ij_java_space_before_colon_in_foreach = true
-ij_java_space_before_comma = false
-ij_java_space_before_do_left_brace = true
-ij_java_space_before_else_keyword = true
-ij_java_space_before_else_left_brace = true
-ij_java_space_before_finally_keyword = true
-ij_java_space_before_finally_left_brace = true
-ij_java_space_before_for_left_brace = true
-ij_java_space_before_for_parentheses = true
-ij_java_space_before_for_semicolon = false
-ij_java_space_before_if_left_brace = true
-ij_java_space_before_if_parentheses = true
-ij_java_space_before_method_call_parentheses = false
-ij_java_space_before_method_left_brace = true
-ij_java_space_before_method_parentheses = false
-ij_java_space_before_opening_angle_bracket_in_type_parameter = false
-ij_java_space_before_quest = true
-ij_java_space_before_switch_left_brace = true
-ij_java_space_before_switch_parentheses = true
-ij_java_space_before_synchronized_left_brace = true
-ij_java_space_before_synchronized_parentheses = true
-ij_java_space_before_try_left_brace = true
-ij_java_space_before_try_parentheses = true
-ij_java_space_before_type_parameter_list = false
-ij_java_space_before_while_keyword = true
-ij_java_space_before_while_left_brace = true
-ij_java_space_before_while_parentheses = true
-ij_java_space_inside_one_line_enum_braces = false
-ij_java_space_within_empty_array_initializer_braces = false
-ij_java_space_within_empty_method_call_parentheses = false
-ij_java_space_within_empty_method_parentheses = false
-ij_java_spaces_around_additive_operators = true
-ij_java_spaces_around_assignment_operators = true
-ij_java_spaces_around_bitwise_operators = true
-ij_java_spaces_around_equality_operators = true
-ij_java_spaces_around_lambda_arrow = true
-ij_java_spaces_around_logical_operators = true
-ij_java_spaces_around_method_ref_dbl_colon = false
-ij_java_spaces_around_multiplicative_operators = true
-ij_java_spaces_around_relational_operators = true
-ij_java_spaces_around_shift_operators = true
-ij_java_spaces_around_type_bounds_in_type_parameters = true
-ij_java_spaces_around_unary_operator = false
-ij_java_spaces_within_angle_brackets = false
-ij_java_spaces_within_annotation_parentheses = false
-ij_java_spaces_within_array_initializer_braces = false
-ij_java_spaces_within_braces = false
-ij_java_spaces_within_brackets = false
-ij_java_spaces_within_cast_parentheses = false
-ij_java_spaces_within_catch_parentheses = false
-ij_java_spaces_within_for_parentheses = false
-ij_java_spaces_within_if_parentheses = false
-ij_java_spaces_within_method_call_parentheses = false
-ij_java_spaces_within_method_parentheses = false
-ij_java_spaces_within_parentheses = false
-ij_java_spaces_within_switch_parentheses = false
-ij_java_spaces_within_synchronized_parentheses = false
-ij_java_spaces_within_try_parentheses = false
-ij_java_spaces_within_while_parentheses = false
-ij_java_special_else_if_treatment = true
-ij_java_subclass_name_suffix = Impl
-ij_java_ternary_operation_signs_on_next_line = false
-ij_java_ternary_operation_wrap = normal
-ij_java_test_name_suffix = Test
-ij_java_throws_keyword_wrap = normal
-ij_java_throws_list_wrap = normal
-ij_java_use_external_annotations = false
-ij_java_use_fq_class_names = false
-ij_java_use_single_class_imports = true
-ij_java_variable_annotation_wrap = normal
-ij_java_visibility = public
-ij_java_while_brace_force = always
-ij_java_while_on_new_line = false
-ij_java_wrap_comments = true
-ij_java_wrap_first_method_in_call_chain = false
-ij_java_wrap_long_lines = true
-
-[*.json]
-indent_size = 2
-ij_json_keep_blank_lines_in_code = 0
-ij_json_keep_indents_on_empty_lines = false
-ij_json_keep_line_breaks = true
-ij_json_space_after_colon = true
-ij_json_space_after_comma = true
-ij_json_space_before_colon = true
-ij_json_space_before_comma = false
-ij_json_spaces_within_braces = false
-ij_json_spaces_within_brackets = false
-ij_json_wrap_long_lines = false
-
-[*.properties]
-ij_properties_align_group_field_declarations = false
-
-[*.scala]
-indent_size = 2
-tab_width = 2
-ij_continuation_indent_size = 2
-ij_scala_align_composite_pattern = true
-ij_scala_align_extends_with = 0
-ij_scala_align_group_field_declarations = false
-ij_scala_align_if_else = false
-ij_scala_align_in_columns_case_branch = false
-ij_scala_align_multiline_binary_operation = false
-ij_scala_align_multiline_chained_methods = false
-ij_scala_align_multiline_for = true
-ij_scala_align_multiline_parameters = true
-ij_scala_align_multiline_parameters_in_calls = false
-ij_scala_align_multiline_parenthesized_expression = false
-ij_scala_align_tuple_elements = false
-ij_scala_align_types_in_multiline_declarations = false
-ij_scala_all_other_imports = all other imports
-ij_scala_alternate_continuation_indent_for_params = 4
-ij_scala_binary_operation_wrap = off
-ij_scala_blank_line = _______ blank line _______
-ij_scala_blank_lines_after_anonymous_class_header = 0
-ij_scala_blank_lines_after_class_header = 0
-ij_scala_blank_lines_after_imports = 1
-ij_scala_blank_lines_after_package = 1
-ij_scala_blank_lines_around_class = 1
-ij_scala_blank_lines_around_field = 0
-ij_scala_blank_lines_around_field_in_inner_scopes = 0
-ij_scala_blank_lines_around_field_in_interface = 0
-ij_scala_blank_lines_around_method = 1
-ij_scala_blank_lines_around_method_in_inner_scopes = 1
-ij_scala_blank_lines_around_method_in_interface = 1
-ij_scala_blank_lines_before_imports = 1
-ij_scala_blank_lines_before_method_body = 0
-ij_scala_blank_lines_before_package = 0
-ij_scala_block_brace_style = end_of_line
-ij_scala_block_comment_at_first_column = true
-ij_scala_call_parameters_new_line_after_lparen = 0
-ij_scala_call_parameters_right_paren_on_new_line = false
-ij_scala_call_parameters_wrap = off
-ij_scala_case_clause_brace_force = never
-ij_scala_catch_on_new_line = false
-ij_scala_class_annotation_wrap = split_into_lines
-ij_scala_class_brace_style = end_of_line
-ij_scala_closure_brace_force = never
-ij_scala_do_not_align_block_expr_params = true
-ij_scala_do_not_indent_case_clause_body = false
-ij_scala_do_not_indent_tuples_close_brace = true
-ij_scala_do_while_brace_force = never
-ij_scala_else_on_new_line = false
-ij_scala_enable_scaladoc_formatting = true
-ij_scala_enforce_functional_syntax_for_unit = true
-ij_scala_exclude_prefix = exclude:
-ij_scala_extends_keyword_wrap = off
-ij_scala_extends_list_wrap = off
-ij_scala_field_annotation_wrap = split_into_lines
-ij_scala_finally_brace_force = never
-ij_scala_finally_on_new_line = false
-ij_scala_for_brace_force = never
-ij_scala_for_statement_wrap = off
-ij_scala_formatter = 0
-ij_scala_if_brace_force = never
-ij_scala_indent_braced_function_args = true
-ij_scala_indent_case_from_switch = true
-ij_scala_indent_first_parameter = true
-ij_scala_indent_first_parameter_clause = false
-ij_scala_indent_type_arguments = true
-ij_scala_indent_type_parameters = true
-ij_scala_insert_whitespaces_in_simple_one_line_method = true
-ij_scala_keep_blank_lines_before_right_brace = 2
-ij_scala_keep_blank_lines_in_code = 2
-ij_scala_keep_blank_lines_in_declarations = 2
-ij_scala_keep_comments_on_same_line = true
-ij_scala_keep_first_column_comment = false
-ij_scala_keep_indents_on_empty_lines = false
-ij_scala_keep_line_breaks = true
-ij_scala_keep_one_line_lambdas_in_arg_list = false
-ij_scala_keep_simple_blocks_in_one_line = false
-ij_scala_keep_simple_methods_in_one_line = false
-ij_scala_keep_xml_formatting = false
-ij_scala_line_comment_at_first_column = true
-ij_scala_method_annotation_wrap = split_into_lines
-ij_scala_method_brace_force = never
-ij_scala_method_brace_style = end_of_line
-ij_scala_method_call_chain_wrap = off
-ij_scala_method_parameters_new_line_after_left_paren = false
-ij_scala_method_parameters_right_paren_on_new_line = false
-ij_scala_method_parameters_wrap = off
-ij_scala_modifier_list_wrap = false
-ij_scala_multiline_string_align_dangling_closing_quotes = false
-ij_scala_multiline_string_closing_quotes_on_new_line = true
-ij_scala_multiline_string_insert_margin_on_enter = true
-ij_scala_multiline_string_margin_char = |
-ij_scala_multiline_string_margin_indent = 2
-ij_scala_multiline_string_opening_quotes_on_new_line = true
-ij_scala_multiline_string_process_margin_on_copy_paste = true
-ij_scala_newline_after_annotations = false
-ij_scala_not_continuation_indent_for_params = false
-ij_scala_parameter_annotation_wrap = off
-ij_scala_parentheses_expression_new_line_after_left_paren = false
-ij_scala_parentheses_expression_right_paren_on_new_line = false
-ij_scala_place_closure_parameters_on_new_line = false
-ij_scala_place_self_type_on_new_line = true
-ij_scala_prefer_parameters_wrap = false
-ij_scala_preserve_space_after_method_declaration_name = false
-ij_scala_reformat_on_compile = false
-ij_scala_replace_case_arrow_with_unicode_char = false
-ij_scala_replace_for_generator_arrow_with_unicode_char = false
-ij_scala_replace_lambda_with_greek_letter = false
-ij_scala_replace_map_arrow_with_unicode_char = false
-ij_scala_scalafmt_reformat_on_files_save = false
-ij_scala_scalafmt_show_invalid_code_warnings = true
-ij_scala_scalafmt_use_intellij_formatter_for_range_format = true
-ij_scala_sd_align_exception_comments = true
-ij_scala_sd_align_other_tags_comments = true
-ij_scala_sd_align_parameters_comments = true
-ij_scala_sd_align_return_comments = true
-ij_scala_sd_blank_line_after_parameters_comments = false
-ij_scala_sd_blank_line_after_return_comments = false
-ij_scala_sd_blank_line_before_parameters = false
-ij_scala_sd_blank_line_before_tags = true
-ij_scala_sd_blank_line_between_parameters = false
-ij_scala_sd_keep_blank_lines_between_tags = false
-ij_scala_sd_preserve_spaces_in_tags = false
-ij_scala_space_after_comma = true
-ij_scala_space_after_for_semicolon = true
-ij_scala_space_after_modifiers_constructor = false
-ij_scala_space_after_type_colon = true
-ij_scala_space_before_brace_method_call = true
-ij_scala_space_before_class_left_brace = true
-ij_scala_space_before_infix_like_method_parentheses = false
-ij_scala_space_before_infix_method_call_parentheses = false
-ij_scala_space_before_infix_operator_like_method_call_parentheses = true
-ij_scala_space_before_method_call_parentheses = false
-ij_scala_space_before_method_left_brace = true
-ij_scala_space_before_method_parentheses = false
-ij_scala_space_before_type_colon = false
-ij_scala_space_before_type_parameter_in_def_list = false
-ij_scala_space_before_type_parameter_list = false
-ij_scala_space_inside_closure_braces = true
-ij_scala_space_inside_self_type_braces = true
-ij_scala_space_within_empty_method_call_parentheses = false
-ij_scala_spaces_around_at_in_patterns = false
-ij_scala_spaces_in_imports = false
-ij_scala_spaces_in_one_line_blocks = false
-ij_scala_spaces_within_brackets = false
-ij_scala_spaces_within_for_parentheses = false
-ij_scala_spaces_within_if_parentheses = false
-ij_scala_spaces_within_method_call_parentheses = false
-ij_scala_spaces_within_method_parentheses = false
-ij_scala_spaces_within_parentheses = false
-ij_scala_spaces_within_while_parentheses = false
-ij_scala_special_else_if_treatment = true
-ij_scala_trailing_comma_arg_list_enabled = true
-ij_scala_trailing_comma_import_selector_enabled = false
-ij_scala_trailing_comma_mode = trailing_comma_keep
-ij_scala_trailing_comma_params_enabled = true
-ij_scala_trailing_comma_pattern_arg_list_enabled = false
-ij_scala_trailing_comma_tuple_enabled = false
-ij_scala_trailing_comma_tuple_type_enabled = false
-ij_scala_trailing_comma_type_params_enabled = false
-ij_scala_try_brace_force = never
-ij_scala_type_annotation_exclude_constant = true
-ij_scala_type_annotation_exclude_in_dialect_sources = true
-ij_scala_type_annotation_exclude_in_test_sources = false
-ij_scala_type_annotation_exclude_member_of_anonymous_class = false
-ij_scala_type_annotation_exclude_member_of_private_class = false
-ij_scala_type_annotation_exclude_when_type_is_stable = true
-ij_scala_type_annotation_function_parameter = false
-ij_scala_type_annotation_implicit_modifier = true
-ij_scala_type_annotation_local_definition = false
-ij_scala_type_annotation_private_member = false
-ij_scala_type_annotation_protected_member = true
-ij_scala_type_annotation_public_member = true
-ij_scala_type_annotation_structural_type = true
-ij_scala_type_annotation_underscore_parameter = false
-ij_scala_type_annotation_unit_type = true
-ij_scala_use_alternate_continuation_indent_for_params = false
-ij_scala_use_scaladoc2_formatting = false
-ij_scala_variable_annotation_wrap = off
-ij_scala_while_brace_force = never
-ij_scala_while_on_new_line = false
-ij_scala_wrap_before_with_keyword = false
-ij_scala_wrap_first_method_in_call_chain = false
-ij_scala_wrap_long_lines = false
-
-[.editorconfig]
-ij_editorconfig_align_group_field_declarations = false
-ij_editorconfig_space_after_colon = false
-ij_editorconfig_space_after_comma = true
-ij_editorconfig_space_before_colon = false
-ij_editorconfig_space_before_comma = false
-ij_editorconfig_spaces_around_assignment_operators = true
-
-[{*.gant,*.groovy,*.gradle,*.gdsl,*.gy}]
-ij_groovy_align_group_field_declarations = false
-ij_groovy_align_multiline_array_initializer_expression = false
-ij_groovy_align_multiline_assignment = false
-ij_groovy_align_multiline_binary_operation = false
-ij_groovy_align_multiline_chained_methods = false
-ij_groovy_align_multiline_extends_list = false
-ij_groovy_align_multiline_for = true
-ij_groovy_align_multiline_method_parentheses = false
-ij_groovy_align_multiline_parameters = true
-ij_groovy_align_multiline_parameters_in_calls = false
-ij_groovy_align_multiline_resources = true
-ij_groovy_align_multiline_ternary_operation = false
-ij_groovy_align_multiline_throws_list = false
-ij_groovy_align_throws_keyword = false
-ij_groovy_array_initializer_new_line_after_left_brace = false
-ij_groovy_array_initializer_right_brace_on_new_line = false
-ij_groovy_array_initializer_wrap = off
-ij_groovy_assert_statement_wrap = off
-ij_groovy_assignment_wrap = off
-ij_groovy_binary_operation_wrap = off
-ij_groovy_blank_lines_after_class_header = 0
-ij_groovy_blank_lines_after_imports = 1
-ij_groovy_blank_lines_after_package = 1
-ij_groovy_blank_lines_around_class = 1
-ij_groovy_blank_lines_around_field = 0
-ij_groovy_blank_lines_around_field_in_interface = 0
-ij_groovy_blank_lines_around_method = 1
-ij_groovy_blank_lines_around_method_in_interface = 1
-ij_groovy_blank_lines_before_imports = 1
-ij_groovy_blank_lines_before_method_body = 0
-ij_groovy_blank_lines_before_package = 0
-ij_groovy_block_brace_style = end_of_line
-ij_groovy_block_comment_at_first_column = true
-ij_groovy_call_parameters_new_line_after_left_paren = false
-ij_groovy_call_parameters_right_paren_on_new_line = false
-ij_groovy_call_parameters_wrap = off
-ij_groovy_catch_on_new_line = false
-ij_groovy_class_annotation_wrap = split_into_lines
-ij_groovy_class_brace_style = end_of_line
-ij_groovy_do_while_brace_force = never
-ij_groovy_else_on_new_line = false
-ij_groovy_enum_constants_wrap = off
-ij_groovy_extends_keyword_wrap = off
-ij_groovy_extends_list_wrap = off
-ij_groovy_field_annotation_wrap = split_into_lines
-ij_groovy_finally_on_new_line = false
-ij_groovy_for_brace_force = never
-ij_groovy_for_statement_new_line_after_left_paren = false
-ij_groovy_for_statement_right_paren_on_new_line = false
-ij_groovy_for_statement_wrap = off
-ij_groovy_if_brace_force = never
-ij_groovy_indent_case_from_switch = true
-ij_groovy_keep_blank_lines_before_right_brace = 2
-ij_groovy_keep_blank_lines_in_code = 2
-ij_groovy_keep_blank_lines_in_declarations = 2
-ij_groovy_keep_control_statement_in_one_line = true
-ij_groovy_keep_first_column_comment = true
-ij_groovy_keep_indents_on_empty_lines = false
-ij_groovy_keep_line_breaks = true
-ij_groovy_keep_multiple_expressions_in_one_line = false
-ij_groovy_keep_simple_blocks_in_one_line = false
-ij_groovy_keep_simple_classes_in_one_line = true
-ij_groovy_keep_simple_lambdas_in_one_line = true
-ij_groovy_keep_simple_methods_in_one_line = true
-ij_groovy_lambda_brace_style = end_of_line
-ij_groovy_line_comment_add_space = false
-ij_groovy_line_comment_at_first_column = true
-ij_groovy_method_annotation_wrap = split_into_lines
-ij_groovy_method_brace_style = end_of_line
-ij_groovy_method_call_chain_wrap = off
-ij_groovy_method_parameters_new_line_after_left_paren = false
-ij_groovy_method_parameters_right_paren_on_new_line = false
-ij_groovy_method_parameters_wrap = off
-ij_groovy_modifier_list_wrap = false
-ij_groovy_parameter_annotation_wrap = off
-ij_groovy_parentheses_expression_new_line_after_left_paren = false
-ij_groovy_parentheses_expression_right_paren_on_new_line = false
-ij_groovy_prefer_parameters_wrap = false
-ij_groovy_resource_list_new_line_after_left_paren = false
-ij_groovy_resource_list_right_paren_on_new_line = false
-ij_groovy_resource_list_wrap = off
-ij_groovy_space_after_colon = true
-ij_groovy_space_after_comma = true
-ij_groovy_space_after_comma_in_type_arguments = true
-ij_groovy_space_after_for_semicolon = true
-ij_groovy_space_after_quest = true
-ij_groovy_space_after_type_cast = true
-ij_groovy_space_before_annotation_parameter_list = false
-ij_groovy_space_before_array_initializer_left_brace = false
-ij_groovy_space_before_catch_keyword = true
-ij_groovy_space_before_catch_left_brace = true
-ij_groovy_space_before_catch_parentheses = true
-ij_groovy_space_before_class_left_brace = true
-ij_groovy_space_before_colon = true
-ij_groovy_space_before_comma = false
-ij_groovy_space_before_do_left_brace = true
-ij_groovy_space_before_else_keyword = true
-ij_groovy_space_before_else_left_brace = true
-ij_groovy_space_before_finally_keyword = true
-ij_groovy_space_before_finally_left_brace = true
-ij_groovy_space_before_for_left_brace = true
-ij_groovy_space_before_for_parentheses = true
-ij_groovy_space_before_for_semicolon = false
-ij_groovy_space_before_if_left_brace = true
-ij_groovy_space_before_if_parentheses = true
-ij_groovy_space_before_method_call_parentheses = false
-ij_groovy_space_before_method_left_brace = true
-ij_groovy_space_before_method_parentheses = false
-ij_groovy_space_before_quest = true
-ij_groovy_space_before_switch_left_brace = true
-ij_groovy_space_before_switch_parentheses = true
-ij_groovy_space_before_synchronized_left_brace = true
-ij_groovy_space_before_synchronized_parentheses = true
-ij_groovy_space_before_try_left_brace = true
-ij_groovy_space_before_try_parentheses = true
-ij_groovy_space_before_while_keyword = true
-ij_groovy_space_before_while_left_brace = true
-ij_groovy_space_before_while_parentheses = true
-ij_groovy_space_within_empty_array_initializer_braces = false
-ij_groovy_space_within_empty_method_call_parentheses = false
-ij_groovy_spaces_around_additive_operators = true
-ij_groovy_spaces_around_assignment_operators = true
-ij_groovy_spaces_around_bitwise_operators = true
-ij_groovy_spaces_around_equality_operators = true
-ij_groovy_spaces_around_lambda_arrow = true
-ij_groovy_spaces_around_logical_operators = true
-ij_groovy_spaces_around_multiplicative_operators = true
-ij_groovy_spaces_around_relational_operators = true
-ij_groovy_spaces_around_shift_operators = true
-ij_groovy_spaces_within_annotation_parentheses = false
-ij_groovy_spaces_within_array_initializer_braces = false
-ij_groovy_spaces_within_braces = true
-ij_groovy_spaces_within_brackets = false
-ij_groovy_spaces_within_cast_parentheses = false
-ij_groovy_spaces_within_catch_parentheses = false
-ij_groovy_spaces_within_for_parentheses = false
-ij_groovy_spaces_within_if_parentheses = false
-ij_groovy_spaces_within_method_call_parentheses = false
-ij_groovy_spaces_within_method_parentheses = false
-ij_groovy_spaces_within_parentheses = false
-ij_groovy_spaces_within_switch_parentheses = false
-ij_groovy_spaces_within_synchronized_parentheses = false
-ij_groovy_spaces_within_try_parentheses = false
-ij_groovy_spaces_within_while_parentheses = false
-ij_groovy_special_else_if_treatment = true
-ij_groovy_ternary_operation_wrap = off
-ij_groovy_throws_keyword_wrap = off
-ij_groovy_throws_list_wrap = off
-ij_groovy_variable_annotation_wrap = off
-ij_groovy_while_brace_force = never
-ij_groovy_while_on_new_line = false
-ij_groovy_wrap_long_lines = false
-
-[{*.jhm,*.xslt,*.xul,*.tagx,*.rng,*.xsl,*.xsd,*.jspx,*.ant,*.xml,*.tld,*.fxml,*.wsdl,*.jrxml,*.jnlp,*.pom}]
-ij_xml_block_comment_at_first_column = true
-ij_xml_keep_indents_on_empty_lines = false
-ij_xml_line_comment_at_first_column = true
-
-[{*.kt,*.kts}]
-ij_kotlin_align_in_columns_case_branch = false
-ij_kotlin_align_multiline_binary_operation = false
-ij_kotlin_align_multiline_extends_list = false
-ij_kotlin_align_multiline_method_parentheses = false
-ij_kotlin_align_multiline_parameters = true
-ij_kotlin_align_multiline_parameters_in_calls = false
-ij_kotlin_assignment_wrap = off
-ij_kotlin_blank_lines_after_class_header = 0
-ij_kotlin_blank_lines_around_block_when_branches = 0
-ij_kotlin_block_comment_at_first_column = true
-ij_kotlin_call_parameters_new_line_after_left_paren = false
-ij_kotlin_call_parameters_right_paren_on_new_line = false
-ij_kotlin_call_parameters_wrap = off
-ij_kotlin_catch_on_new_line = false
-ij_kotlin_class_annotation_wrap = split_into_lines
-ij_kotlin_continuation_indent_for_chained_calls = true
-ij_kotlin_continuation_indent_for_expression_bodies = true
-ij_kotlin_continuation_indent_in_argument_lists = true
-ij_kotlin_continuation_indent_in_elvis = true
-ij_kotlin_continuation_indent_in_if_conditions = true
-ij_kotlin_continuation_indent_in_parameter_lists = true
-ij_kotlin_continuation_indent_in_supertype_lists = true
-ij_kotlin_else_on_new_line = false
-ij_kotlin_enum_constants_wrap = off
-ij_kotlin_extends_list_wrap = off
-ij_kotlin_field_annotation_wrap = split_into_lines
-ij_kotlin_finally_on_new_line = false
-ij_kotlin_if_rparen_on_new_line = false
-ij_kotlin_import_nested_classes = false
-ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
-ij_kotlin_keep_blank_lines_before_right_brace = 2
-ij_kotlin_keep_blank_lines_in_code = 2
-ij_kotlin_keep_blank_lines_in_declarations = 2
-ij_kotlin_keep_first_column_comment = true
-ij_kotlin_keep_indents_on_empty_lines = false
-ij_kotlin_keep_line_breaks = true
-ij_kotlin_lbrace_on_next_line = false
-ij_kotlin_line_comment_add_space = false
-ij_kotlin_line_comment_at_first_column = true
-ij_kotlin_method_annotation_wrap = split_into_lines
-ij_kotlin_method_call_chain_wrap = off
-ij_kotlin_method_parameters_new_line_after_left_paren = false
-ij_kotlin_method_parameters_right_paren_on_new_line = false
-ij_kotlin_method_parameters_wrap = off
-ij_kotlin_name_count_to_use_star_import = 5
-ij_kotlin_name_count_to_use_star_import_for_members = 3
-ij_kotlin_parameter_annotation_wrap = off
-ij_kotlin_space_after_comma = true
-ij_kotlin_space_after_extend_colon = true
-ij_kotlin_space_after_type_colon = true
-ij_kotlin_space_before_catch_parentheses = true
-ij_kotlin_space_before_comma = false
-ij_kotlin_space_before_extend_colon = true
-ij_kotlin_space_before_for_parentheses = true
-ij_kotlin_space_before_if_parentheses = true
-ij_kotlin_space_before_lambda_arrow = true
-ij_kotlin_space_before_type_colon = false
-ij_kotlin_space_before_when_parentheses = true
-ij_kotlin_space_before_while_parentheses = true
-ij_kotlin_spaces_around_additive_operators = true
-ij_kotlin_spaces_around_assignment_operators = true
-ij_kotlin_spaces_around_equality_operators = true
-ij_kotlin_spaces_around_function_type_arrow = true
-ij_kotlin_spaces_around_logical_operators = true
-ij_kotlin_spaces_around_multiplicative_operators = true
-ij_kotlin_spaces_around_range = false
-ij_kotlin_spaces_around_relational_operators = true
-ij_kotlin_spaces_around_unary_operator = false
-ij_kotlin_spaces_around_when_arrow = true
-ij_kotlin_variable_annotation_wrap = off
-ij_kotlin_while_on_new_line = false
-ij_kotlin_wrap_elvis_expressions = 1
-ij_kotlin_wrap_expression_body_functions = 0
-ij_kotlin_wrap_first_method_in_call_chain = false
-
-[{*.shtm,*.htm,*.sht,*.shtml,*.html}]
-ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
-ij_html_align_attributes = true
-ij_html_align_text = false
-ij_html_attribute_wrap = normal
-ij_html_block_comment_at_first_column = true
-ij_html_do_not_align_children_of_min_lines = 0
-ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
-ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot
-ij_html_enforce_quotes = false
-ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var
-ij_html_keep_blank_lines = 2
-ij_html_keep_indents_on_empty_lines = false
-ij_html_keep_line_breaks = true
-ij_html_keep_line_breaks_in_text = true
-ij_html_keep_whitespaces = false
-ij_html_keep_whitespaces_inside = span,pre,textarea
-ij_html_line_comment_at_first_column = true
-ij_html_new_line_after_last_attribute = never
-ij_html_new_line_before_first_attribute = never
-ij_html_quote_style = double
-ij_html_remove_new_line_before_tags = br
-ij_html_space_after_tag_name = false
-ij_html_space_around_equality_in_attribute = false
-ij_html_space_inside_empty_tag = false
-ij_html_text_wrap = normal
-
-[{*.yml,*.yaml}]
-indent_size = 2
-ij_continuation_indent_size = 2
-ij_yaml_keep_indents_on_empty_lines = false
-ij_yaml_keep_line_breaks = true
-
-[{*.zsh,*.bash,*.sh}]
-ij_shell_binary_ops_start_line = false
-ij_shell_keep_column_alignment_padding = false
-ij_shell_minify_program = false
-ij_shell_redirect_followed_by_space = false
-ij_shell_switch_cases_indented = false
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 1730d965..00000000
--- a/.gitattributes
+++ /dev/null
@@ -1,46 +0,0 @@
-# https://www.gitattributes.io/api/java
-#
-# Handle line endings automatically for files detected as text
-# and leave all files detected as binary untouched.
-* text=auto
-
-#
-# The above will handle all files NOT found below
-#
-# These files are text and should be normalized (Convert crlf => lf)
-*.css           text
-*.df            text
-*.htm           text
-*.html          text
-*.java          text
-*.js            text
-*.json          text
-*.jsp           text
-*.jspf          text
-*.jspx          text
-*.properties    text
-*.sh            text
-*.tld           text
-*.txt           text
-*.tag           text
-*.tagx          text
-*.xml           text
-*.yml           text
-
-# Declare files that will always have CRLF line endings on checkout.
-*.sln           text eol=crlf
-*.bat           text eol=crlf
-
-# These files are binary and should be left untouched
-# (binary is a macro for -text -diff)
-*.class         binary
-*.dll           binary
-*.ear           binary
-*.gif           binary
-*.ico           binary
-*.jar           binary
-*.jpg           binary
-*.jpeg          binary
-*.png           binary
-*.so            binary
-*.war           binary
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..55ede737
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: Petersoj
\ No newline at end of file
diff --git a/.github/image/logo.png b/.github/image/logo.png
new file mode 100644
index 00000000..a9cdaa37
Binary files /dev/null and b/.github/image/logo.png differ
diff --git a/.gitignore b/.gitignore
index fcbc20f3..12a5664a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,3 @@ build/
 .settings
 .DS_Store
 .idea/
-alpaca.properties
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d54435b1..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-language: java
-install: true
-dist: trusty
-
-jdk:
-  - oraclejdk8
-
-before_script:
-  - chmod +x gradlew
-
-script:
-  - ./gradlew build check jacocoTestReport --stacktrace
-
-after_success:
-  - bash <(curl -s https://codecov.io/bash)
-  
-before_cache:
-  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock
-  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
-
-cache:
-  directories:
-    - $HOME/.gradle/caches/
-    - $HOME/.gradle/wrapper/
diff --git a/LICENSE b/LICENSE.txt
similarity index 96%
rename from LICENSE
rename to LICENSE.txt
index 706b1675..38f23352 100644
--- a/LICENSE
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) Jacob Peterson 2021
+Copyright (c) Jacob Peterson
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index dad90b54..a7a4efb9 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,13 @@
-<p align="center"><a href="https://petersoj.github.io/alpaca-java/" target="_blank"><img src="https://i.imgur.com/D8XzGvJ.png" alt="Logo"></a></p>
+<p align="center"><a href="https://petersoj.github.io/alpaca-java/" target="_blank"><img src="https://raw.githubusercontent.com/Petersoj/alpaca-java/master/.github/image/logo.png" alt="Logo"></a></p>
 <p align="center">
-    <a href="https://search.maven.org/artifact/net.jacobpeterson/alpaca-java" target="_blank"><img alt="Maven Central" src="https://img.shields.io/maven-central/v/net.jacobpeterson/alpaca-java"></a>
-    <a href="https://javadoc.io/doc/net.jacobpeterson/alpaca-java" target="_blank"><img src="https://javadoc.io/badge/net.jacobpeterson/alpaca-java.svg" alt="Javadocs"></a>
-    <a href="https://travis-ci.com/github/Petersoj/alpaca-java" target="_blank"><img src="https://travis-ci.com/Petersoj/alpaca-java.svg?branch=master" alt="Build Status"></a>
-    <a href="https://codecov.io/gh/petersoj/alpaca-java"><img src="https://codecov.io/gh/petersoj/alpaca-java/branch/master/graph/badge.svg" alt="CodeCov badge"/></a>
-    <a href="https://opensource.org/licenses/MIT" target="_blank"><img alt="GitHub" src="https://img.shields.io/github/license/petersoj/alpaca-java"></a>    
+    <a href="https://github.com/Petersoj/alpaca-java" target="_blank"><img alt="GitHub Repository" src="https://img.shields.io/badge/GitHub-000000?logo=github"></a>
+    <a href="https://search.maven.org/artifact/net.jacobpeterson.alpaca/alpaca-java" target="_blank"><img alt="Maven Central" src="https://img.shields.io/maven-central/v/net.jacobpeterson.alpaca/alpaca-java"></a>
+    <a href="https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java" target="_blank"><img src="https://javadoc.io/badge/net.jacobpeterson.alpaca/alpaca-java.svg" alt="Javadocs"></a>
+    <a href="https://opensource.org/licenses/MIT" target="_blank"><img alt="GitHub" src="https://img.shields.io/github/license/petersoj/alpaca-java"></a>
 </p>
 
 # Overview
-This is a Java implementation for the <a href="https://alpaca.markets/">Alpaca</a> API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/).
+This library is a Java client implementation of the [Alpaca](https://alpaca.markets) API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specification](https://docs.alpaca.markets/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing.
 
 Give this repository a star ⭐ if it helped you build a trading algorithm in Java!
 
@@ -16,411 +15,230 @@ Give this repository a star ⭐ if it helped you build a trading algorithm in Ja
 If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file:
 
 ```
-dependencies {
-    implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.1'
-}
+implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.1"
 ```
 
 If you are using Maven as your build tool, add the following dependency to your `pom.xml` file:
 
 ```
 <dependency>
-    <groupId>net.jacobpeterson</groupId>
+    <groupId>net.jacobpeterson.alpaca</groupId>
     <artifactId>alpaca-java</artifactId>
-    <version>8.1</version>
-    <scope>compile</scope>
+    <version>10.0.1</version>
 </dependency>
 ```
 
-Note that you don't have to use the Maven Central artifacts and instead can just install a clone of this project to your local Maven repository as shown in the [Building](#building) section.
-
-# Configuration
-Creating an `alpaca.properties` file on the classpath with the following format allows you to easily load properties using the `AlpacaAPI` default constructor:
-```
-key_id = <your Key ID>
-secret_key = <your Secret Key>
-endpoint_api_type = <must be either "paper" or "live">
-data_api_type = <must be either "iex" or "sip">
-```
-The default values for `alpaca.properties` can be found [here](src/main/resources/alpaca.default.properties).
+Note that you don't have to use the Maven Central artifacts. Instead, you can clone this repository, build this project, and install the artifacts to your local Maven repository as shown in the [Building](#building) section.
 
 # Logger
-For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/).
+For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/).
+
+# `BigDecimal` vs. `Double`
+It is generally considered bad practice to represent monetary values using floating-point number data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701). However, using floating-point number data types can have significant performance benefits compared to using fixed-point number data types (via `BigDecimal`), especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` data type when using the Trading or Broker APIs. The rationale behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations through technical indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. By the way, using `BigDecimal` wouldn't matter if Alpaca used floating-point data types in their internal systems, but the fact that they use `String` data types in some of the REST API JSON responses and their Trading and Broker OpenAPI specifications don't use the `double` data type, this leads me to believe that using `BigDecimal` does actually matter. 
 
 # Examples
-Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java) for all classes and method signatures.
+Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java) for all classes and method signatures.
 
 ## [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java)
-The [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) class contains several instances of various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) and [`AlpacaWebsockets`](src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java) to interface with Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside the various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) allow `null` to be passed in as a parameter if it is optional. 
+[`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) is the main class used to interface with the various Alpaca API endpoints. If you are using the Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca account, this class is optimized so that it can be instantiated quickly, especially when an existing `OkHttpClient` is given in the constructor. Additionally, all API endpoint instances are instantiated lazily. This class is thread-safe.
 
-The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html).
+The Alpaca API documentation is located [here](https://docs.alpaca.markets/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html).
 
 Example usage:
-```java
-// This constructor uses the 'alpaca.properties' file on the classpath for configuration
-AlpacaAPI alpacaAPI = new AlpacaAPI();
-
-// This constructor passes in a 'keyID' and 'secret' String
-String keyID = "<some key ID>";
-String secretKey = "<some secret>";
-AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secret);
-
-// This constructor is for OAuth tokens
-String oAuthToken = "<some OAuth token>";
-AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken);
-```
-
-Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet:
-```java
-alpacaAPI.getOkHttpClient().dispatcher().executorService().shutdown();
-alpacaAPI.getOkHttpClient().connectionPool().evictAll();
-```
-
-See the [OkHttpClient Documentation](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/#shutdown-isnt-necessary) for more information.
-
-## [`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java)
-[`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) is thrown anytime an exception occurs when using various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java). It should be caught and handled within your trading algorithm application accordingly.
-
-## [`AccountEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java)
-The Account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade.
 
-Example usage:
+Use this code if you are using the Trading or Market Data APIs for a single Alpaca account:
 ```java
-try {
-    // Get 'Account' information and print it out
-    Account account = alpacaAPI.account().get();
-    System.out.println(account);
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
+final String keyID = "<your_key_id>";
+final String secretKey = "<your_secret_key>";
+final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE'
+final MarketDataWebsocketSourceType sourceType = MarketDataWebsocketSourceType.IEX; // or 'SIP'
+final AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, endpointType, sourceType);
 ```
 
-## [`MarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java)
-The Data API v2 provides market data through an easy-to-use API for historical data.
-
-Example usage:
+Use this code if you are using the Trading API with OAuth to act on behalf of an Alpaca account:
 ```java
-try {
-    // Get AAPL one hour bars from 7/6/2021 market open
-    // to 7/8/2021 market close and print them out
-    BarsResponse aaplBarsResponse = alpacaAPI.marketData().getBars(
-            "AAPL",
-            ZonedDateTime.of(2021, 7, 6, 9, 30, 0, 0, ZoneId.of("America/New_York")),
-            ZonedDateTime.of(2021, 7, 8, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")),
-            null,
-            null,
-            BarsTimeFrame.ONE_HOUR);
-    aaplBarsResponse.getBars().forEach(System.out::println);
-
-    // Get AAPL first 10 trades on 7/8/2021 at market open and print them out
-    TradesResponse aaplTradesResponse = alpacaAPI.marketData().getTrades(
-            "AAPL",
-            ZonedDateTime.of(2021, 7, 8, 9, 30, 0, 0, ZoneId.of("America/New_York")),
-            ZonedDateTime.of(2021, 7, 8, 9, 31, 0, 0, ZoneId.of("America/New_York")),
-            10,
-            null);
-    aaplTradesResponse.getTrades().forEach(System.out::println);
-
-    // Print out latest AAPL trade
-    Trade latestAAPLTrade = alpacaAPI.marketData().getLatestTrade("AAPL").getTrade();
-    System.out.printf("Latest AAPL Trade: %s\n", latestAAPLTrade);
-
-    // Print out snapshot of AAPL, GME, and TSLA
-    Map<String, Snapshot> snapshots = alpacaAPI.marketData()
-            .getSnapshots(Arrays.asList("AAPL", "GME", "TSLA"));
-    snapshots.forEach((symbol, snapshot) ->
-            System.out.printf("Symbol: %s\nSnapshot: %s\n\n", symbol, snapshot));
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
+final String oAuthToken = "<an_oauth_token>";
+final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE'
+final AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken, endpointType);
 ```
 
-## [`OrdersEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java)
-The Orders API allows a user to monitor, place, and cancel their orders with Alpaca.
-
-Example usage:
+Use this code if you are using the Broker API:
 ```java
-try {
-    // Print all the 'Order's of AAPL and TSLA until 7/6/2021 in
-    // ascending (oldest to newest) order
-    List<Order> orders = alpacaAPI.orders().get(
-            CurrentOrderStatus.ALL,
-            null,
-            null,
-            ZonedDateTime.of(2021, 7, 6, 0, 0, 0, 0, ZoneId.of("America/New_York")),
-            SortDirection.ASCENDING,
-            true,
-            Arrays.asList("AAPL", "TSLA"));
-    orders.forEach(System.out::println);
-
-    // Request a new limit order for TSLA for 100 shares at a limit price
-    // of $653.00 and TIF of DAY.
-    Order tslaLimitOrder = alpacaAPI.orders().requestLimitOrder(
-            "TSLA",
-            100,
-            OrderSide.BUY,
-            OrderTimeInForce.DAY,
-            653.00,
-            false);
-    System.out.printf("Requested %s %s order at %s\n",
-            tslaLimitOrder.getSymbol(),
-            tslaLimitOrder.getType(),
-            tslaLimitOrder.getSubmittedAt());
-
-    // Check if TSLA limit order has filled 5 seconds later and if it hasn't
-    // replace it with a higher limit order so it fills!
-    Thread.sleep(5000);
-    tslaLimitOrder = alpacaAPI.orders().get(tslaLimitOrder.getId(), false);
-    if (tslaLimitOrder.getFilledAt() == null && tslaLimitOrder.getStatus().equals(OrderStatus.NEW)) {
-        Order replacedOrder = alpacaAPI.orders().replace(
-                tslaLimitOrder.getId(),
-                100,
-                OrderTimeInForce.DAY,
-                655.00,
-                null, null, null);
-        System.out.printf("Replaced TSLA order: %s\n", replacedOrder);
-    }
-
-    // Cancel all open orders after 2 seconds
-    Thread.sleep(2000);
-    List<CancelledOrder> cancelledOrders = alpacaAPI.orders().cancelAll();
-    cancelledOrders.forEach((cancelledOrder) -> System.out.printf("Cancelled: %s\n", cancelledOrder));
-
-    // Request a new fractional market order for 0.5 shares of GME
-    alpacaAPI.orders().requestFractionalMarketOrder("GME", 0.5, OrderSide.BUY);
-    // Request a new notional market order for $25 worth of GME shares
-    alpacaAPI.orders().requestNotionalMarketOrder("GME", 25d, OrderSide.BUY);
-} catch (AlpacaClientException | InterruptedException exception) {
-    exception.printStackTrace();
-}
+final String brokerAPIKey = "<your_broker_api_key>";
+final String brokerAPISecret = "<your_broker_api_secret>";
+final BrokerAPIEndpointType endpointType = BrokerAPIEndpointType.SANDBOX; // or 'PRODUCTION'
+final AlpacaAPI alpacaAPI = new AlpacaAPI(brokerAPIKey, brokerAPISecret, endpointType);
 ```
 
-## [`PositionsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java)
-The Positions API provides information about an account's current open positions.
+## [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java)
+The [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) is used for placing trades, updating account details, getting open positions, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.html) for a list of all available method signatures.
 
 Example usage:
 ```java
-try {
-    // Close 50% of all open TSLA and AAPL positions
-    List<Position> openPositions = alpacaAPI.positions().get();
-    for (Position openPosition : openPositions) {
-        if (openPosition.getSymbol().equals("TSLA") || openPosition.getSymbol().equals("AAPL")) {
-            Order closePositionOrder = alpacaAPI.positions()
-                    .close(openPosition.getSymbol(), null, 50d);
-            System.out.printf("Closing 50%% of %s position: %s\n",
-                    openPosition.getSymbol(), closePositionOrder);
-        }
-    }
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
+// Place a market order to buy one share of Apple
+final Order openingOrder = alpacaAPI.trader().orders()
+        .postOrder(new PostOrderRequest()
+                .symbol("AAPL")
+                .qty("1")
+                .side(OrderSide.BUY)
+                .type(OrderType.MARKET)
+                .timeInForce(TimeInForce.GTC));
+System.out.println("Opening Apple order: " + openingOrder);
+
+// Wait for massive gains
+Thread.sleep(10_000);
+
+// Close the Apple position
+final Order closingOrder = alpacaAPI.trader().positions()
+        .deleteOpenPosition("AAPL", null, new BigDecimal("100"));
+System.out.println("Closing Apple order: " + openingOrder);
+
+// Wait for closing trade to fill
+Thread.sleep(10_000);
+
+// Print out PnL
+final String openFillPrice = alpacaAPI.trader().orders()
+        .getOrderByOrderID(UUID.fromString(openingOrder.getId()), false)
+        .getFilledAvgPrice();
+final String closeFillPrice = alpacaAPI.trader().orders()
+        .getOrderByOrderID(UUID.fromString(closingOrder.getId()), false)
+        .getFilledAvgPrice();
+System.out.println("PnL from Apple trade: " +
+        new BigDecimal(closeFillPrice).subtract(new BigDecimal(openFillPrice)));
 ```
 
-## [`AssetsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java)
-The Assets API serves as the master list of assets available for trade and data consumption from Alpaca.
+## [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java)
+The [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) is used for getting market data for stocks, cryptocurrencies, options, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.html) for a list of all available method signatures.
 
 Example usage:
 ```java
-try {
-    // Print out a CSV of all active US equity 'Asset's
-    List<Asset> activeUSEquities = alpacaAPI.assets().get(AssetStatus.ACTIVE, null);
-    System.out.println(activeUSEquities
-            .stream().map(Asset::getSymbol)
-            .collect(Collectors.joining(", ")));
-
-    // Print out TSLA 'Asset' information
-    Asset tslaAsset = alpacaAPI.assets().getBySymbol("TSLA");
-    System.out.println(tslaAsset);
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
+// Print out the latest Tesla trade
+final StockTrade latestTSLATrade = alpacaAPI.marketData().stock()
+        .stockLatestTradeSingle("TSLA", StockFeed.IEX, null).getTrade();
+System.out.printf("Latest TSLA trade: price=%s, size=%s\n",
+        latestTSLATrade.getP(), latestTSLATrade.getS());
+
+// Print out the highest Bitcoin ask price on the order book
+final CryptoOrderbook latestBitcoinOrderBooks = alpacaAPI.marketData().crypto()
+        .cryptoLatestOrderbooks(CryptoLoc.US, "BTC/USD").getOrderbooks().get("BTC/USD");
+final Double highestBitcoinAskPrice = latestBitcoinOrderBooks.getA().stream()
+        .map(CryptoOrderbookEntry::getP)
+        .max(Double::compare)
+        .orElse(null);
+System.out.println("Bitcoin highest ask price: " + highestBitcoinAskPrice);
+
+// Print out the latest Microsoft option trade
+final String latestMSFTOptionTrade = alpacaAPI.marketData().option()
+        .optionChain("MSFT").getSnapshots().entrySet().stream()
+        .filter(entry -> entry.getValue().getLatestTrade() != null)
+        .map(entry -> Map.entry(entry.getKey(), entry.getValue().getLatestTrade()))
+        .max(Comparator.comparing(entry -> entry.getValue().getT()))
+        .map(entry -> String.format("ticker=%s, time=%s, price=%s",
+                entry.getKey(), entry.getValue().getT(), entry.getValue().getP()))
+        .orElse(null);
+System.out.println("Latest Microsoft option trade: " + latestMSFTOptionTrade);
 ```
 
-## [`WatchlistEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java)
-The Watchlist API provides CRUD operation for the account's watchlist.
+## [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java)
+The [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java) is used for creating new Alpaca accounts for your end users, funding their accounts, placing trades, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.html) for a list of all available method signatures.
 
 Example usage:
 ```java
-try {
-    // Print out CSV all 'Watchlist' names
-    List<Watchlist> watchlists = alpacaAPI.watchlist().get();
-    System.out.println(watchlists.stream()
-            .map(Watchlist::getName)
-            .collect(Collectors.joining(", ")));
-
-    // Create a watchlist for day trading with TSLA and AAPL initially
-    Watchlist dayTradeWatchlist = alpacaAPI.watchlist().create("Day Trade", "TSLA", "AAPL");
-
-    // Remove TSLA and add MSFT to 'dayTradeWatchlist'
-    alpacaAPI.watchlist().removeSymbol(dayTradeWatchlist.getId(), "TSLA");
-    alpacaAPI.watchlist().addAsset(dayTradeWatchlist.getId(), "MSFT");
-
-    // Set the updated watchlist variable
-    dayTradeWatchlist = alpacaAPI.watchlist().get(dayTradeWatchlist.getId());
-
-    // Print CSV of 'dayTradeWatchlist' 'Asset's
-    System.out.println(dayTradeWatchlist.getAssets()
-            .stream().map(Asset::getSymbol)
-            .collect(Collectors.joining(", ")));
-
-    // Delete the 'dayTradeWatchlist'
-    alpacaAPI.watchlist().delete(dayTradeWatchlist.getId());
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
+// Listen to the trade events (SSE) and print them out
+final SSERequest sseRequest = alpacaAPI.broker().events()
+        .subscribeToTradeV2(null, null, null, null, new SSEListenerAdapter<>());
+
+// Wait for SSE channel to be ready
+Thread.sleep(2000);
+
+// Buy one share of GME for an account
+alpacaAPI.broker().trading()
+        .createOrderForAccount(UUID.fromString("<some_account_uuid>"),
+                new CreateOrderRequest()
+                        .symbol("GME")
+                        .qty(BigDecimal.ONE)
+                        .side(OrderSide.SELL)
+                        .timeInForce(TimeInForce.GTC)
+                        .type(OrderType.MARKET));
+
+// Wait to be filled
+Thread.sleep(2000);
+
+// Close the SSE stream and the OkHttpClient to exit cleanly
+sseRequest.close();
 ```
 
-## [`CalendarEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java)
-The calendar API serves the full list of market days from 1970 to 2029.
+## [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java)
+The [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) is used for listening to trade updates in realtime. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.html) for a list of all available method signatures.
 
 Example usage:
 ```java
-try {
-    // Get the 'Calendar's of the week of Christmas 2020 and print them out
-    List<Calendar> calendar = alpacaAPI.calendar().get(
-            LocalDate.of(2020, 12, 20),
-            LocalDate.of(2020, 12, 27));
-    calendar.forEach(System.out::println);
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
+// Connect to the 'updates' stream and wait until it's authorized
+alpacaAPI.updatesStream().connect();
+if (!alpacaAPI.updatesStream().waitForAuthorization(5, TimeUnit.SECONDS)) {
+    throw new RuntimeException();
 }
-```
 
-## [`ClockEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java)
-The clock API serves the current market timestamp, whether the market is currently open, as well as the times of the next market open and close.
+// Print out trade updates
+alpacaAPI.updatesStream().setListener(System.out::println);
+alpacaAPI.updatesStream().subscribeToTradeUpdates(true);
 
-Example usage:
-```java
-try {
-    // Get the market 'Clock' and print it out
-    Clock clock = alpacaAPI.clock().get();
-    System.out.println(clock);
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
-```
+// Place a trade
+alpacaAPI.trader().orders().postOrder(new PostOrderRequest()
+        .symbol("AAPL")
+        .qty("1")
+        .side(OrderSide.BUY)
+        .type(OrderType.MARKET)
+        .timeInForce(TimeInForce.GTC));
 
-## [`AccountConfigurationEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java)
-The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs.
+// Wait a few seconds
+Thread.sleep(5000);
 
-Example usage:
-```java
-try {
-    // Update the 'AccountConfiguration' to block new orders
-    AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get();
-    accountConfiguration.setSuspendTrade(true);
-    alpacaAPI.accountConfiguration().set(accountConfiguration);
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
-```
+// Close the trade
+alpacaAPI.trader().positions().deleteAllOpenPositions(true);
 
-## [`AccountActivitiesEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java)
-The Account Activities API provides access to a historical record of transaction activities that have impacted your account.
+// Wait a few seconds
+Thread.sleep(5000);
 
-Example usage:
-```java
-try {
-    // Print all order fill and cash deposit 'AccountActivity's on 7/8/2021
-    List<AccountActivity> accountActivities = alpacaAPI.accountActivities().get(
-            ZonedDateTime.of(2021, 7, 8, 0, 0, 0, 0, ZoneId.of("America/New_York")),
-            null,
-            null,
-            SortDirection.ASCENDING,
-            null,
-            null,
-            ActivityType.FILL, ActivityType.CSD);
-    for (AccountActivity accountActivity : accountActivities) {
-        if (accountActivity instanceof TradeActivity) {
-            System.out.println("TradeActivity: " + (TradeActivity) accountActivity);
-        } else if (accountActivity instanceof NonTradeActivity) {
-            System.out.println("NonTradeActivity: " + (NonTradeActivity) accountActivity);
-        }
-    }
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
-}
+// Disconnect the 'updates' stream and exit cleanly
+alpacaAPI.updatesStream().disconnect();
 ```
 
-## [`PortfolioHistoryEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java)
-The Portfolio History API returns the timeseries data for equity and profit loss information of the account.
+## [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java)
+The [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) is used for listening to stock market data, crypto market data, and news data in realtime. Refer to the Javadocs ([stock](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.html), [crypto](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.html), [news](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.html)) for a list of all available method signatures.
 
 Example usage:
 ```java
-try {
-    // Get 3 days of one-hour 'PortfolioHistory' on 7/8/2021 and print out its data points
-    PortfolioHistory portfolioHistory = alpacaAPI.portfolioHistory().get(
-            3,
-            PortfolioPeriodUnit.DAY,
-            PortfolioTimeFrame.ONE_HOUR,
-            LocalDate.of(2021, 7, 8),
-            false);
-    System.out.printf("Timeframe: %s, Base value: %s \n",
-            portfolioHistory.getTimeframe(),
-            portfolioHistory.getBaseValue());
-    portfolioHistory.getDataPoints().forEach(System.out::println);
-} catch (AlpacaClientException exception) {
-    exception.printStackTrace();
+// Connect to the 'stock market data' stream and wait until it's authorized
+alpacaAPI.stockMarketDataStream().connect();
+if (!alpacaAPI.stockMarketDataStream().waitForAuthorization(5, TimeUnit.SECONDS)) {
+    throw new RuntimeException();
 }
-```
 
-## [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java)
-Alpaca offers WebSocket streaming of order updates.
+// Print out trade messages
+alpacaAPI.stockMarketDataStream().setListener(new StockMarketDataListenerAdapter() {
+    @Override
+    public void onTrade(StockTradeMessage trade) {
+        System.out.println("Received trade: " + trade);
+    }
+});
 
-Example usage:
-```java
-// Add a 'StreamingListener' that simply prints streaming information
-StreamingListener streamingListener = (messageType, message) ->
-        System.out.printf("%s: %s\n", messageType.name(), message);
-alpacaAPI.streaming().setListener(streamingListener);
-
-// Listen to the 'authorization' and 'trade update' streams.
-// Note this will connect the websocket and wait for authorization
-// if it isn't already connected.
-alpacaAPI.streaming().streams(
-        StreamingMessageType.AUTHORIZATION,
-        StreamingMessageType.TRADE_UPDATES);
-```
+// Subscribe to AAPL trades
+alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("AAPL"));
+System.out.println("Subscribed to Apple trades.");
 
-The following methods show how you can control the state of the [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) directly.
-```java
-alpacaAPI.streaming().connect();
-alpacaAPI.streaming().waitForAuthorization();
-System.out.println(alpacaAPI.streaming().isValid());
-alpacaAPI.streaming().disconnect();
-```
+// Wait a few seconds
+Thread.sleep(5000);
 
-## [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java)
-Alpaca's Data API v2 provides websocket streaming for trades, quotes, and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement.
+// Unsubscribe from AAPL and subscribe to TSLA and MSFT
+alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("TSLA", "MSFT"));
+System.out.println("Subscribed to Tesla and Microsoft trades.");
 
-Example usage:
-```java
-// Add a 'MarketDataListener' that simply prints market data information
-MarketDataListener marketDataListener = (messageType, message) ->
-        System.out.printf("%s: %s\n", messageType.name(), message);
-alpacaAPI.marketDataStreaming().setListener(marketDataListener);
-
-// Listen to 'SubscriptionsMessage', 'SuccessMessage', and 'ErrorMessage' control messages
-// that contain information about the stream's current state.
-alpacaAPI.marketDataStreaming().subscribeToControl(
-        MarketDataMessageType.SUCCESS,
-        MarketDataMessageType.SUBSCRIPTION,
-        MarketDataMessageType.ERROR);
-// Listen to the AAPL and TSLA trades and all ('*') bars.
-// Note this will connect the websocket and wait for authorization
-// if it isn't already connected.
-alpacaAPI.marketDataStreaming().subscribe(
-        Arrays.asList("AAPL", "TSLA"),
-        null,
-        Arrays.asList("*"));
-```
+// Wait a few seconds
+Thread.sleep(5000);
 
-The following methods show how you can control the state of the [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) directly.
-```java
-alpacaAPI.marketDataStreaming().connect();
-alpacaAPI.marketDataStreaming().waitForAuthorization();
-System.out.println(alpacaAPI.marketDataStreaming().isValid());
-alpacaAPI.marketDataStreaming().disconnect();
+// Disconnect the 'stock market data' stream and exit cleanly
+alpacaAPI.stockMarketDataStream().disconnect();
 ```
 
 # Building
@@ -429,30 +247,20 @@ To build this project yourself, clone this repository and run:
 ./gradlew build
 ```
 
-To install built artifacts to your local maven repo, run:
-```
-./gradlew install -x test
-```
-
-# Testing
-
-To run mocked tests using Mockito, run:
-```
-./gradlew test
-```
-Note that mocked tests never send real API requests to Alpaca. Mocked tests are meant to test the basic integrity of this API locally.
-
-To run live endpoint tests with Alpaca Paper credentials, create the `alpaca.properties` file in `src/test/resources` with the corresponding credentials. Then run:
+To install the built artifacts to your local Maven repository on your machine (the `~/.m2/` directory), run:
 ```
-./gradlew test -PtestPackage=live
+./gradlew publishToMavenLocal
 ```
-Note that the live tests will modify your account minimally. It's meant to test live endpoints on a real paper account during market hours to confirm that API methods are working properly. Please read through the live endpoint tests [here](https://github.com/Petersoj/alpaca-java/tree/master/src/test/java/live/net/jacobpeterson/alpaca) before running this testing suite on your own paper account.
 
 # TODO
-- Finish Unit Testing (both live and mocked)
-- Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` for number variables so that users can use either `Double` or `BigDecimal` for performance or precision in price data.
-- Add [TimeSeriesDataStore](https://github.com/Petersoj/TimeSeriesDataStore) using Alpaca Data API
+- Implement Options market data.
+- Implement better reconnect logic for Websockets and SSE streaming.
+- Implement Unit Testing for REST API and Websocket streaming (both live and mocked).
+- Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` or `BigDecimal` for number data types so that users can use either `Double` or `BigDecimal` for performance or precision in price data.
 
+# Contributing
 Contributions are welcome!
 
-If you are creating a Pull Request, be sure to create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. 
+Do the following before starting your pull request:
+1. Create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork.
+2. Use the `dev` branch as the base branch in your pull request.
diff --git a/_config.yml b/_config.yml
index 259a24e4..72d781d7 100644
--- a/_config.yml
+++ b/_config.yml
@@ -1 +1 @@
-theme: jekyll-theme-tactile
\ No newline at end of file
+theme: jekyll-theme-tactile
diff --git a/build.gradle b/build.gradle
index 6e25af5e..c3948da4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,36 +1,31 @@
-import org.jsonschema2pojo.gradle.GenerateJsonSchemaJavaTask
+import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
 
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-
-    dependencies {
-        // For Maven and Maven Central support
-        classpath group: 'com.bmuschko', name: 'gradle-nexus-plugin', version: '2.3.1'
-        // For JSONSchema2POJO
-        classpath group: 'org.jsonschema2pojo', name: 'jsonschema2pojo-gradle-plugin', version: '1.1.1'
-    }
-}
+import java.nio.file.Paths
 
 plugins {
-    id 'java'
+    id "java"
+    id "java-library"
+
+    // Adapted JSONSchema2POJO plugin
+    id "jsonschema2pojo-adapted"
 
-    // For Maven and Maven Central support
-    id 'maven-publish'
-    id 'com.bmuschko.nexus' version '2.3.1'
-    id 'io.codearte.nexus-staging' version '0.11.0'
+    // OpenAPI Generator
+    id "org.openapi.generator" version "7.3.0"
 
-    // For code coverage reports
-    id 'jacoco'
+    // Gradle versions plugin
+    id "com.github.ben-manes.versions" version "0.51.0"
+
+    // For Maven Central publishing
+    id "maven-publish"
+    id "signing"
 }
 
-apply plugin: 'jsonschema2pojo'
-tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'jsonSchema2Pojo' task
+final def projectGroup = "net.jacobpeterson.alpaca"
+final def projectArtifactID = "alpaca-java"
+final def projectVersion = "10.0.1-SNAPSHOT"
 
-archivesBaseName = 'alpaca-java'
-group = 'net.jacobpeterson'
-version = '8.1-SNAPSHOT'
+group = projectGroup
+version = projectVersion
 
 repositories {
     mavenCentral()
@@ -38,210 +33,311 @@ repositories {
 
 dependencies {
     // Logging framework
-    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.31'
-    // This is a bridge for the Apache Jakarta Commons Logging library used in dependencies to actually use SLF4j instead
-    implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.31'
+    implementation group: "org.slf4j", name: "slf4j-api", version: "2.1.0-alpha1"
+    // Require dependencies that use the Apache Jakarta Commons Logging library to use SLF4j instead
+    implementation group: "org.slf4j", name: "jcl-over-slf4j", version: "2.1.0-alpha1"
 
-    // Guava and GSON
-    implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-jre'
-    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.7'
+    // Google Guava
+    implementation group: "com.google.guava", name: "guava", version: "33.0.0-jre"
 
-    // OkHttp
-    implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.1'
-    implementation group: 'com.github.devcsrj', name: 'slf4j-okhttp3-logging-interceptor', version: '1.0.1'
+    // GSON
+    implementation group: "com.google.code.gson", name: "gson", version: "2.10.1"
 
-    // Unit test dependencies
-    testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.30'
-    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.7.0'
-    testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.7.0'
+    // Jetbrains Annotations
+    implementation group: "org.jetbrains", name: "annotations", version: "24.1.0"
+
+    // OkHttp
+    implementation group: "com.squareup.okhttp3", name: "okhttp", version: "5.0.0-alpha.12"
+    implementation group: "com.squareup.okhttp3", name: "okhttp-sse", version: "5.0.0-alpha.12"
+    implementation group: "com.squareup.okhttp3", name: "logging-interceptor", version: "5.0.0-alpha.12"
+
+    // Libraries used by the OpenAPI generated client libraries
+    implementation group: "jakarta.annotation", name: "jakarta.annotation-api", version: "1.3.5"
+    implementation group: "org.openapitools", name: "jackson-databind-nullable", version: "0.2.6"
+    implementation group: "io.gsonfire", name: "gson-fire", version: "1.9.0"
 }
 
 // Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice
 configurations.implementation {
-    exclude group: 'commons-logging', module: 'commons-logging'
+    exclude group: "commons-logging", module: "commons-logging"
 }
 
-sourceSets {
-    main {
-        java {
-            srcDirs 'src/main/java'
-        }
-    }
-    test {
-        java {
-            srcDirs 'src/test/java'
-        }
-    }
+java {
+    sourceCompatibility = JavaVersion.VERSION_17
+    targetCompatibility = JavaVersion.VERSION_17
+    withJavadocJar()
+    withSourcesJar()
 }
+javadocJar.dependsOn generatePOJOs
+sourcesJar.dependsOn generatePOJOs
+jar.dependsOn compileJava
+javadocJar.dependsOn compileJava
+sourcesJar.dependsOn compileJava
 
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
-[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
+[compileJava, compileTestJava]*.options*.encoding = "UTF-8"
 
 javadoc {
-    // Explicitly define source version
-    options.addStringOption('source', '8')
-    // Used to suppress Javadoc linting warnings
-    options.addStringOption('Xdoclint:none', '-quiet')
-    // Explicitly define Charset for Javadocs
-    options.addStringOption('charset', 'UTF-8')
-    // Add Java SE 8 link
-    options.addStringOption('link', 'https://docs.oracle.com/javase/8/docs/api/')
+    options.addStringOption("source", "17")
+    options.addStringOption("charset", "UTF-8")
+    options.addStringOption("Xdoclint:none", "-quiet") // Suppress Javadoc linting warnings
+    options.addStringOption("link", "https://docs.oracle.com/en/java/javase/17/docs/api/")
 }
 
-test {
-    useJUnitPlatform()
+//
+// BEGIN Alpaca OpenAPI Specification (OAS) client generation
+//
 
-    testLogging {
-        events "passed", "skipped", "failed"
-        exceptionFormat "full"
+// These IDs come from the URLs on this page: https://docs.alpaca.markets/openapi
+final def specIDsOfFileNames = Map.of(
+        "trader.json", "673e19fc6dc876001ff495b2",
+        "broker.json", "673e1a03a0b2160060120aa0",
+        "market-data.json", "673e19f7fd59830026ac651b")
+final File specDownloadPath = new File(project.layout.buildDirectory.get().getAsFile(),
+        "/download/openapi/")
+final File generatedClientLibrariesPath = new File(project.layout.buildDirectory.get().getAsFile(),
+        "/generated/openapi/")
+
+def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") {
+    configure {
+        outputs.dir(specDownloadPath)
+        outputs.cacheIf { true }
     }
 
-    doFirst {
-        def testPackageArgument = "testPackage"
-        filter.includeTestsMatching(project.hasProperty(testPackageArgument) &&
-                project.getProperty(testPackageArgument).equals("live") ? "*.live.*" : "*.mock.*")
-    }
-}
+    doLast {
+        // Download spec files
+        specIDsOfFileNames.forEach { fileName, alpacaSpecID ->
+            final def outputFile = new File(specDownloadPath, fileName)
+            final def specURL = "https://docs.alpaca.markets/openapi/${alpacaSpecID}"
+            outputFile.getParentFile().mkdirs()
+            try {
+                logger.info("Downloading OpenAPI spec file from: {}", specURL)
+                new URL(specURL).withInputStream { inputStream -> outputFile.withOutputStream { it << inputStream } }
+                logger.info("Successfully downloaded OpenAPI spec file to: {}", outputFile.getPath())
+            } catch (IOException ioException) {
+                throw new RuntimeException("Could not download OpenAPI spec file!", ioException)
+            }
+        }
 
-jacocoTestReport {
-    reports {
-        xml.enabled true
-        html.enabled false
+        // Fix an issue in the Broker spec file that uses an empty string as an object key for some reason
+        // TODO remove this when issue is fixed by Alpaca
+        final def brokerSpecFile = new File(specDownloadPath, "broker.json")
+        brokerSpecFile.text = brokerSpecFile.text
+                .replace(",\"\":{\"type\":\"string\",\"x-stoplight\":{\"id\":\"1zg5jnb6rzdoo\"}}", "")
     }
 }
-jacocoTestReport.dependsOn check
-
-//
-// START publishing
-//
-modifyPom {
-    project {
-        name = 'alpaca-java'
-        description = 'Java API for Alpaca'
-        url = 'https://github.com/Petersoj/alpaca-java'
-        inceptionYear = '2018'
-
-        licenses {
-            license {
-                name = 'MIT License'
-                url = 'https://opensource.org/licenses/MIT'
+compileJava.dependsOn downloadOpenAPISpecFilesTask
+
+final def generateTasks = []
+for (def specFileName : specIDsOfFileNames.keySet()) {
+    final def specName = specFileName.replace(".json", "")
+    final def inputSpecFile = new File(specDownloadPath, specFileName)
+    final def outputDirectory = new File(generatedClientLibrariesPath, specName)
+
+    // Add source set of OpenAPI generated client library
+    sourceSets {
+        main {
+            java {
+                srcDir new File(outputDirectory, "src/main/java/").getPath()
             }
         }
+    }
 
-        developers {
-            developer {
-                id = 'Petersoj'
-                name = 'Jacob Peterson'
-            }
-
-            developer {
-                id = 'mainstringargs'
-                name = 'main(String[] args)'
-            }
+    // Register and configure OpenAPI generate task
+    final def generateTask = tasks.register("generateOpenAPIClientFor-" + specName, GenerateTask) {
+        dependsOn {
+            downloadOpenAPISpecFilesTask
         }
 
-        scm {
-            url = "https://github.com/Petersoj/alpaca-java.git"
-            connection = "scm:git:https://github.com/Petersoj/alpaca-java.git"
-            developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git"
+        configure {
+            inputs.file(inputSpecFile)
+            outputs.dir(outputDirectory)
+            outputs.cacheIf { true }
         }
+
+        generatorName.set("java")
+        inputSpec.set(inputSpecFile.getPath())
+        outputDir.set(outputDirectory.getPath())
+        final def destinationPackage = projectGroup + ".openapi." + specName.replace("-", "")
+        configOptions.set(Map.of(
+                "artifactId", specName,
+                "artifactVersion", "1.0.0-SNAPSHOT",
+                "groupId", destinationPackage,
+                "invokerPackage", destinationPackage,
+                "apiPackage", destinationPackage + ".api",
+                "modelPackage", destinationPackage + ".model",
+                "library", "okhttp-gson",
+                "documentationProvider", "none",
+                "useBeanValidation", "false",
+                "performBeanValidation", "false"))
+        cleanupOutput.set(true)
+        validateSpec.set(false)
+        skipValidateSpec.set(true)
+        generateModelTests.set(false)
+        generateModelDocumentation.set(false)
+        generateApiTests.set(false)
+        generateApiDocumentation.set(false)
     }
+    compileJava.dependsOn generateTask
+    generateTasks.add(generateTask)
 }
 
-extraArchive {
-    sources = true
-    tests = true
-    javadoc = true
-}
+final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGeneratedClientIssues") {
+    configure {
+        mustRunAfter(generateTasks)
+    }
 
-nexus {
-    sign = true
-    repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
-    snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
-}
+    it.onlyIf {
+        generateTasks.stream().allMatch { generateTask ->
+            !generateTask.get().state.upToDate &&
+                    generateTask.get().state.failure == null &&
+                    !generateTask.get().state.skipped &&
+                    generateTask.get().state.executed
+        }
+    }
 
-nexusStaging {
-    packageGroup = "net.jacobpeterson"
-}
+    doLast {
+        // TODO remove these manual and hacky fixes once OpenAPI fixes these issues
+
+        final def sourceFilesPath = "src/main/java/net/jacobpeterson/alpaca/openapi"
+
+        // Fix broker spec generated client issues with discriminators
+        // on some enums: https://github.com/OpenAPITools/openapi-generator/issues/806
+        final def brokerTransferDataJavaFile = new File(generatedClientLibrariesPath,
+                "broker/${sourceFilesPath}/broker/model/TransferData.java")
+        brokerTransferDataJavaFile.text = brokerTransferDataJavaFile.text
+                .replace("this.transferType = this.getClass().getSimpleName();", "")
+        final def brokerTransferResourceJavaFile = new File(generatedClientLibrariesPath,
+                "broker/${sourceFilesPath}/broker/model/TransferResource.java")
+        brokerTransferResourceJavaFile.text = brokerTransferResourceJavaFile.text
+                .replace("this.type = this.getClass().getSimpleName();", "")
+
+        // Remove the JSON validation that the generated client models always run (there isn't an option to
+        // disable it for some reason). Also, remove throwing an IllegalArgumentException for a null/empty string
+        // given to enum 'fromValue' methods.
+        specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach {
+            for (File modelFile : Paths.get(generatedClientLibrariesPath.getPath(),
+                    it, sourceFilesPath, it.replace("-", ""), "model").toFile().listFiles()) {
+                modelFile.text = modelFile.text
+                        .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues " +
+                                "found[\\s\\S]*?\\*/", "")
+                        .replace("import java.io.IOException;",
+                                "import java.io.IOException;\nimport java.util.function.Predicate;")
+                        .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {",
+                                "public static boolean validate = false;\n" +
+                                        "    public static Predicate<JsonElement> isValid = jsonElement -> true;\n" +
+                                        "    public static void validateJsonElement(JsonElement jsonElement) " +
+                                        "throws IOException {\n" +
+                                        "      if (!isValid.test(jsonElement)) throw new RuntimeException();\n" +
+                                        "      if (!validate) return;\n")
+                        .replace("throw new IllegalArgumentException(\"Unexpected value '\" + value + \"'\");",
+                                "return null;")
+            }
+        }
 
-// Used to prevent accidental publication of Alpaca keys
-processResources {
-    exclude('alpaca.properties')
+        // Make certain method signatures in the generated client API classes 'protected' instead of 'public'
+        specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach {
+            for (File apiFile : Paths.get(generatedClientLibrariesPath.getPath(),
+                    it, sourceFilesPath, it.replace("-", ""), "api").toFile().listFiles()) {
+                // Don't make replacements in the broker 'events' API as that class is wrapped
+                if (apiFile.name == "EventsApi.java") {
+                    continue
+                }
+                // Make replacements
+                apiFile.text = apiFile.text
+                        .replace("public okhttp3.Call", "protected okhttp3.Call")
+                        .replace("public ApiResponse", "protected ApiResponse")
+                        .replaceAll("\\n.*public ApiClient[\\s\\S]*?customBaseUrl;\\n {4}}", "")
+            }
+        }
+
+        // Remove the necessary authentication header logic from the generate client's 'ApiClient'
+        specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach {
+            final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath,
+                    it.replace("-", ""), "ApiClient.java").toFile()
+            apiClientFile.text = apiClientFile.text
+                    .replaceAll("// Setup authentications \\(key: authentication name, value: authentication\\)" +
+                            "[\\s\\S]*?authentications\\);", "")
+                    .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "")
+        }
+
+        // Make the 'JSON' classes of the generate clients have a static Gson initializer. Also fix the OffsetDateTime
+        // Gson type adapter to accommodate ISO local dates.
+        specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach {
+            final def jsonClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath,
+                    it.replace("-", ""), "JSON.java").toFile()
+            jsonClientFile.text = jsonClientFile.text
+                    .replaceAll("\\{\\n {8}GsonBuilder", "static {\n        GsonBuilder")
+                    .replaceAll("}[\\s\\S].*return OffsetDateTime\\.parse\\(date, formatter\\);",
+                            "} else if (!date.contains(\"T\")) {\n" +
+                                    "return LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE)" +
+                                    ".atTime(java.time.LocalTime.MIN.atOffset(java.time.ZoneOffset.UTC));\n" +
+                                    "}\n" +
+                                    "return OffsetDateTime.parse(date, formatter);")
+        }
+    }
 }
+compileJava.dependsOn fixOpenAPIGeneratedClientIssuesTask
 
 //
-// END publishing
+// END Alpaca OpenAPI Specification (OAS) client generation
 //
 
 //
-// START POJO generation
+// BEGIN Publishing
 //
 
-// The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory
-// See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference
-
-final def targetDirectoryPath = file("${project.buildDir}/generated-sources/schemajson").getPath()
-
-task generatePOJOs() {
-    final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath()
-    final def jsonPackageNamePrefix = "net.jacobpeterson.alpaca.model"
-
-    // Loop through all files in schema JSON file tree
-    file(jsonSourceDirectory).eachFileRecurse { jsonFile ->
-        if (jsonFile.getName().endsWith(".json")) {
-            def startPackageIndex = jsonFile.getAbsolutePath().indexOf(jsonSourceDirectory) + jsonSourceDirectory.length()
-            def targetPackage = jsonPackageNamePrefix + jsonFile.getParentFile().getAbsolutePath()
-                    .substring(startPackageIndex)
-                    .replace(File.separator, '.').replace('-', '').replace('_', '')
-                    .toLowerCase()
-
-            def jsonToPOJOTask = tasks.create('json2POJOTask-' + targetPackage + "." + jsonFile.getName(),
-                    GenerateJsonSchemaJavaTask)
-
-            // Configure this task to participate in incremental builds so that it only executes when changes occur
-            jsonToPOJOTask.configure {
-                inputs.file(jsonFile)
-                outputs.dir(targetDirectoryPath)
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            groupId = projectGroup
+            artifactId = projectArtifactID
+            version = projectVersion
+            from(components["java"])
+            pom {
+                name = projectArtifactID
+                description = "A Java API for Alpaca, the commission free, algo friendly, stock trading broker."
+                url = "https://github.com/Petersoj/alpaca-java"
+                inceptionYear = "2018"
+                licenses {
+                    license {
+                        name = "MIT License"
+                        url = "https://opensource.org/licenses/MIT"
+                    }
+                }
+                developers {
+                    developer {
+                        id = "Petersoj"
+                        name = "Jacob Peterson"
+                    }
+                    developer {
+                        id = "mainstringargs"
+                        name = "main(String[] args)"
+                    }
+                }
+                scm {
+                    url = "https://github.com/Petersoj/alpaca-java.git"
+                    connection = "scm:git:https://github.com/Petersoj/alpaca-java.git"
+                    developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git"
+                }
             }
-
-            jsonToPOJOTask.doFirst {
-                jsonToPOJOTask.configuration.source = files(jsonFile.getAbsolutePath())
-                jsonToPOJOTask.configuration.targetDirectory = file(targetDirectoryPath)
-                jsonToPOJOTask.configuration.targetPackage = targetPackage
-            }
-
-            dependsOn jsonToPOJOTask
         }
     }
-}
-
-compileJava {
-    dependsOn generatePOJOs
-}
-
-sourceSets {
-    main {
-        java {
-            srcDir targetDirectoryPath
+    repositories {
+        maven {
+            name = "OSSRH"
+            url = projectVersion.contains("SNAPSHOT") ? "https://oss.sonatype.org/content/repositories/snapshots/" :
+                    "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+            credentials {
+                username = project.property("nexus.username")
+                password = project.property("nexus.password")
+            }
         }
     }
 }
-
-jsonSchema2Pojo {
-    includeAdditionalProperties = true
-    targetDirectory = file(targetDirectoryPath)
-    propertyWordDelimiters = ['-', '_'] as char[]
-    annotationStyle = 'gson'
-    sourceType = 'jsonschema'
-    customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ"
-    includeConstructors = true
-    serializable = true
-    includeGetters = true
-    includeSetters = true
-    includeCopyConstructor = true
+signing {
+    sign publishing.publications.mavenJava
 }
 
 //
-// END POJO generation
+// END Publishing
 //
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 00000000..1ca86d32
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,29 @@
+plugins {
+    id "groovy-gradle-plugin"
+}
+
+gradlePlugin {
+    plugins {
+        jsonschema2pojoAdapted {
+            id = "jsonschema2pojo-adapted"
+            implementationClass =
+                    "net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted.JSONSchema2POJOAdaptedPlugin"
+        }
+    }
+}
+
+repositories {
+    mavenCentral()
+    gradlePluginPortal()
+}
+
+dependencies {
+    gradleApi()
+
+    // Google Guava
+    implementation group: "com.google.guava", name: "guava", version: "33.0.0-jre"
+
+    // JsonSchema2Pojo
+    implementation group: "org.jsonschema2pojo", name: "jsonschema2pojo-core", version: "1.2.1"
+    implementation group: "org.jsonschema2pojo", name: "jsonschema2pojo-gradle-plugin", version: "1.2.1"
+}
diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy
new file mode 100644
index 00000000..650a7c7b
--- /dev/null
+++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy
@@ -0,0 +1,121 @@
+package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.model.ReplacedBy
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.SourceSetContainer
+import org.gradle.api.tasks.TaskAction
+import org.jsonschema2pojo.GenerationConfig
+import org.jsonschema2pojo.Jsonschema2Pojo
+import org.jsonschema2pojo.gradle.GradleRuleLogger
+
+import java.nio.file.Files
+import java.nio.file.Path
+
+import static JSONSchema2POJOAdaptedPlugin.EXTENSION_KEY
+import static com.google.common.base.Preconditions.checkArgument
+
+/**
+ * {@link GeneratePOJOsTask} is a Gradle task that invokes <code>jsonschema2pojo</code> with special configurations
+ * and generation logic.
+ * @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/blob/master/jsonschema2pojo-gradle-plugin/src/main/groovy/org/jsonschema2pojo/gradle/GenerateJsonSchemaJavaTask.groovy">GenerateJsonSchemaJavaTask</a>
+ */
+class GeneratePOJOsTask extends DefaultTask {
+
+    @ReplacedBy("configurationString")
+    GenerationConfig configuration
+
+    @Input
+    String getConfigurationString() {
+        configuration.toString()
+    }
+
+    @Internal
+    List<JSONSchemaFileConfig> jsonSchemaFileConfigs
+
+    GeneratePOJOsTask() {
+        group = "Build"
+        description = "Generates Java POJOs from JSON schemas."
+
+        // Validate plugin configuration
+        configuration = project.extensions.getByName(EXTENSION_KEY) as GenerationConfig
+        checkArgument(!configuration.source.hasNext(), "'source' should not be set for '${name}'.")
+        checkArgument(configuration.targetDirectory == null, "'targetDirectory' should not be set for '${name}'.")
+
+        // Set custom defaults on plugin configuration
+        configuration.targetDirectory = project.file(
+                "${project.layout.buildDirectory.getAsFile().get().getPath()}/generated-sources/jsonschema2pojo")
+        configuration.targetPackage = configuration.targetPackage == "" ?
+                "${project.group}.model" : configuration.targetPackage
+        configuration.propertyWordDelimiters = ["-", "_"] as char[]
+        configuration.annotationStyle = "gson"
+        configuration.sourceType = "jsonschema"
+        configuration.customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ"
+        configuration.includeConstructors = true
+        configuration.serializable = true
+        configuration.includeGetters = true
+        configuration.includeSetters = true
+        configuration.includeCopyConstructor = true
+        configuration.generateBuilders = true
+
+        // Get project main java source set
+        final def projectMainJavaSourceSet =
+                project.extensions.getByType(SourceSetContainer).named("main").get().java
+
+        // Walk through source files to create a list of JSON schema files to process
+        jsonSchemaFileConfigs = new ArrayList<>()
+        projectMainJavaSourceSet.srcDirs.each { srcDir ->
+            if (!srcDir.exists()) {
+                return
+            }
+            final def sourceDirPath = srcDir.getAbsolutePath()
+            srcDir.eachFileRecurse { sourceFile ->
+                final def sourceFilePath = sourceFile.getAbsolutePath()
+                if (sourceFilePath.endsWith(".json")) {
+                    def targetPackage = sourceFile.getParentFile().getAbsolutePath()
+                            .substring(sourceDirPath.length())
+                            .replace(File.separator, ".").replace("-", "").replace("_", "")
+                            .toLowerCase()
+                    targetPackage = targetPackage.replace("${project.group}", "")
+                    targetPackage = targetPackage.startsWith(".") ? targetPackage.substring(1) : targetPackage
+                    targetPackage = configuration.targetPackage +
+                            (targetPackage.isEmpty() ? "" : targetPackage)
+                    jsonSchemaFileConfigs.add(new JSONSchemaFileConfig(sourceFile, targetPackage))
+
+                    inputs.file(sourceFile)
+                    outputs.file(project.file(configuration.targetDirectory.getAbsolutePath() + File.separator +
+                            targetPackage.replace(".", File.separator) + File.separator +
+                            sourceFile.getName().replace(".json", ".java")))
+                }
+            }
+        }
+
+        outputs.dir(configuration.targetDirectory)
+        outputs.cacheIf { true }
+
+        projectMainJavaSourceSet.srcDirs(configuration.targetDirectory)
+    }
+
+    @TaskAction
+    def execute() {
+        logger.info("Deleting existing generated POJOs...")
+        Files.walk(configuration.targetDirectory.toPath())
+                .sorted(Comparator.reverseOrder())
+                .map(Path::toFile)
+                .peek(file -> logger.info("Deleting file: {}", file.getPath()))
+                .forEach(File::delete)
+        logger.info("Deleted existing generated POJOs.")
+
+        logger.info("Generating POJOs from JSON schemas...")
+        final def gradleRuleLogger = new GradleRuleLogger(logger)
+        jsonSchemaFileConfigs.forEach { jsonSchemaFileConfig ->
+            configuration.source = List.of(jsonSchemaFileConfig.sourceFile.toURI().toURL())
+            configuration.targetPackage = jsonSchemaFileConfig.targetPackage
+            Jsonschema2Pojo.generate(configuration, gradleRuleLogger)
+            logger.info("Generated POJO for: {}", jsonSchemaFileConfig.targetPackage + "." +
+                    jsonSchemaFileConfig.sourceFile.getName())
+        }
+        logger.info("Generated POJOs from JSON schemas.")
+    }
+}
diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy
new file mode 100644
index 00000000..963e8276
--- /dev/null
+++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy
@@ -0,0 +1,26 @@
+package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted
+
+import org.gradle.api.GradleException
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.jsonschema2pojo.gradle.JsonSchemaExtension
+
+/**
+ * {@link JSONSchema2POJOAdaptedPlugin} is a Gradle plugin that adapts the <code>jsonschema2pojo</code> Gradle plugin
+ * to add/change some features.
+ */
+class JSONSchema2POJOAdaptedPlugin implements Plugin<Project> {
+
+    public static final EXTENSION_KEY = "jsonSchema2POJOAdaptedConfig"
+
+    @Override
+    void apply(Project project) {
+        project.extensions.create(EXTENSION_KEY, JsonSchemaExtension)
+        if (project.plugins.hasPlugin("java")) {
+            project.tasks.register("generatePOJOs", GeneratePOJOsTask)
+            project.tasks.compileJava.dependsOn "generatePOJOs"
+        } else {
+            throw new GradleException("'java' Gradle plugin required.")
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy
new file mode 100644
index 00000000..d4dfd8fd
--- /dev/null
+++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy
@@ -0,0 +1,29 @@
+package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted
+
+/**
+ * {@link JSONSchemaFileConfig} provides the configuration fields for a single JSON schema file.
+ */
+class JSONSchemaFileConfig {
+
+    private File sourceFile
+    private String targetPackage
+
+    /**
+     * Instantiates a new {@link JSONSchemaFileConfig}.
+     *
+     * @param sourceFile the source file
+     * @param targetPackage the target package
+     */
+    JSONSchemaFileConfig(File sourceFile, String targetPackage) {
+        this.sourceFile = sourceFile
+        this.targetPackage = targetPackage
+    }
+
+    File getSourceFile() {
+        return sourceFile
+    }
+
+    String getTargetPackage() {
+        return targetPackage
+    }
+}
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index c749985f..00000000
--- a/codecov.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-coverage:
-  status:
-    # Turn off Codecov status for now
-    project: off
-    patch: off
-comment: false
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..31634b4e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.logging.level=info
+org.gradle.logging.stacktrace=all
+org.gradle.jvmargs=-XX:MaxRAMPercentage=100.0 -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c0..d997cfc6 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1f3fdbc5..1a704683 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0c..739907df 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
 
 #
-# Copyright 2015 the original author or authors.
+# Copyright © 2015 the original authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,81 +15,114 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+# SPDX-License-Identifier: Apache-2.0
+#
 
 ##############################################################################
-##
-##  Gradle start up script for UN*X
-##
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
 ##############################################################################
 
 # Attempt to set APP_HOME
+
 # Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
 done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
 
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
 
 warn () {
     echo "$*"
-}
+} >&2
 
 die () {
     echo
     echo "$*"
     echo
     exit 1
-}
+} >&2
 
 # OS specific support (must be 'true' or 'false').
 cygwin=false
 msys=false
 darwin=false
 nonstop=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-  NONSTOP* )
-    nonstop=true
-    ;;
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
 esac
 
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
 
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
         # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
+        JAVACMD=$JAVA_HOME/jre/sh/java
     else
-        JAVACMD="$JAVA_HOME/bin/java"
+        JAVACMD=$JAVA_HOME/bin/java
     fi
     if [ ! -x "$JAVACMD" ] ; then
         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
     fi
 else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+    JAVACMD=java
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
+    fi
 fi
 
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC2039,SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC2039,SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
 fi
 
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
 
 # For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
     # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
         fi
-        i=`expr $i + 1`
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
     done
-    case $i in
-        0) set -- ;;
-        1) set -- "$args0" ;;
-        2) set -- "$args0" "$args1" ;;
-        3) set -- "$args0" "$args1" "$args2" ;;
-        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
 fi
 
-# Escape application args
-save () {
-    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
-    echo " "
-}
-APP_ARGS=`save "$@"`
 
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
 
 exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 107acd32..e509b2dd 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,93 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem      https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem  Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/schema_json/endpoint/account/account.json b/schema_json/endpoint/account/account.json
deleted file mode 100644
index a6e6d506..00000000
--- a/schema_json/endpoint/account/account.json
+++ /dev/null
@@ -1,110 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account/\">Account</a>.",
-  "properties": {
-    "id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Account ID."
-    },
-    "account_number": {
-      "existingJavaType": "java.lang.String",
-      "title": "Account number."
-    },
-    "status": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.account.enums.AccountStatus",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.account.enums.AccountStatus}."
-    },
-    "currency": {
-      "existingJavaType": "java.lang.String",
-      "title": "\"USD\""
-    },
-    "cash": {
-      "existingJavaType": "java.lang.String",
-      "title": "Cash balance."
-    },
-    "portfolio_value": {
-      "existingJavaType": "java.lang.String",
-      "title": "Total value of cash + holding positions. (This field is deprecated. It is equivalent to the equity field.)"
-    },
-    "pattern_day_trader": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Whether or not the account has been flagged as a pattern day trader."
-    },
-    "trade_suspended_by_user": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "User setting. If true, the account is not allowed to place orders."
-    },
-    "trading_blocked": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "If true, the account is not allowed to place orders."
-    },
-    "transfers_blocked": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "If true, the account is not allowed to request money transfers."
-    },
-    "account_blocked": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "If true, the account activity by user is prohibited."
-    },
-    "created_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Timestamp this account was created at."
-    },
-    "shorting_enabled": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Flag to denote whether or not the account is permitted to short."
-    },
-    "long_market_value": {
-      "existingJavaType": "java.lang.String",
-      "title": "Real-time MtM value of all long positions held in the account."
-    },
-    "short_market_value": {
-      "existingJavaType": "java.lang.String",
-      "title": "Real-time MtM value of all short positions held in the account."
-    },
-    "equity": {
-      "existingJavaType": "java.lang.String",
-      "title": "Cash + long_market_value + short_market_value."
-    },
-    "last_equity": {
-      "existingJavaType": "java.lang.String",
-      "title": "Equity as of previous trading day at 16:00:00 ET."
-    },
-    "multiplier": {
-      "existingJavaType": "java.lang.String",
-      "title": "Buying power multiplier that represents account margin classification; valid values 1 (standard limited margin account with 1x buying power), 2 (reg T margin account with 2x intraday and overnight buying power; this is the default for all non-PDT accounts with $2,000 or more equity), 4 (PDT account with 4x intraday buying power and 2x reg T overnight buying power)."
-    },
-    "buying_power": {
-      "existingJavaType": "java.lang.String",
-      "title": "Current available $ buying power; If multiplier = 4, this is your daytrade buying power which is calculated as (last_equity - (last) maintenance_margin) * 4; If multiplier = 2, buying_power = max(equity – initial_margin,0) * 2; If multiplier = 1, buying_power = cash."
-    },
-    "initial_margin": {
-      "existingJavaType": "java.lang.String",
-      "title": "Reg T initial margin requirement (continuously updated value)."
-    },
-    "maintenance_margin": {
-      "existingJavaType": "java.lang.String",
-      "title": "Maintenance margin requirement (continuously updated value)."
-    },
-    "sma": {
-      "existingJavaType": "java.lang.String",
-      "title": "Value of special memorandum account (will be used at a later date to provide additional buying_power)."
-    },
-    "daytrade_count": {
-      "existingJavaType": "java.lang.Integer",
-      "title": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)."
-    },
-    "last_maintenance_margin": {
-      "existingJavaType": "java.lang.String",
-      "title": "Your maintenance margin requirement on the previous trading day."
-    },
-    "daytrading_buying_power": {
-      "existingJavaType": "java.lang.String",
-      "title": "Your buying power for day trades (continuously updated value)."
-    },
-    "regt_buying_power": {
-      "existingJavaType": "java.lang.String",
-      "title": "Your buying power under Regulation T (your excess equity - equity minus margin value - times your margin multiplier)."
-    }
-  }
-}
diff --git a/schema_json/endpoint/account/enums/account_status.json b/schema_json/endpoint/account/enums/account_status.json
deleted file mode 100644
index 65963f0d..00000000
--- a/schema_json/endpoint/account/enums/account_status.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account/\">Account</a>.",
-  "enum": [
-    "ONBOARDING",
-    "SUBMISSION_FAILED",
-    "SUBMITTED",
-    "ACCOUNT_UPDATED",
-    "APPROVAL_PENDING",
-    "ACTIVE",
-    "REJECTED",
-    "DISABLED"
-  ],
-  "javaEnums": [
-    {
-      "description": "The account is onboarding."
-    },
-    {
-      "description": "The account application submission failed for some reason."
-    },
-    {
-      "description": "The account application has been submitted for review."
-    },
-    {
-      "description": "The account information is being updated."
-    },
-    {
-      "description": "The final account approval is pending."
-    },
-    {
-      "description": "The account is active for trading."
-    },
-    {
-      "description": "The account application has been rejected."
-    },
-    {
-      "description": "The account has been disabled."
-    }
-  ]
-}
diff --git a/schema_json/endpoint/account_activities/account_activity.json b/schema_json/endpoint/account_activities/account_activity.json
deleted file mode 100644
index bfa36b8f..00000000
--- a/schema_json/endpoint/account_activities/account_activity.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-activities/\">Account Activities</a>.",
-  "properties": {
-    "activity_type": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType}."
-    },
-    "id": {
-      "existingJavaType": "java.lang.String",
-      "title": "An ID for the activity, always in \"::\" format. Can be sent as page_token in requests to facilitate the paging of results."
-    }
-  }
-}
diff --git a/schema_json/endpoint/account_activities/enums/activity_type.json b/schema_json/endpoint/account_activities/enums/activity_type.json
deleted file mode 100644
index 03c44af1..00000000
--- a/schema_json/endpoint/account_activities/enums/activity_type.json
+++ /dev/null
@@ -1,144 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-activities/\">Account Activities</a>.",
-  "enum": [
-    "FILL",
-    "TRANS",
-    "MISC",
-    "ACATC",
-    "ACATS",
-    "CSD",
-    "CSR",
-    "CSW",
-    "DIV",
-    "DIVCGL",
-    "DIVCGS",
-    "DIVFEE",
-    "DIVFT",
-    "DIVNRA",
-    "DIVROC",
-    "DIVTW",
-    "DIVTXEX",
-    "INT",
-    "INTNRA",
-    "INTTW",
-    "JNL",
-    "JNLC",
-    "JNLS",
-    "MA",
-    "NC",
-    "OPASN",
-    "OPEXP",
-    "OPXRC",
-    "PTC",
-    "PTR",
-    "FEE",
-    "REORG",
-    "SC",
-    "SSO"
-  ],
-  "javaEnums": [
-    {
-      "description": "Order fills (both partial and full fills)."
-    },
-    {
-      "description": "Cash transactions (both CSD and CSW)."
-    },
-    {
-      "description": "Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL)."
-    },
-    {
-      "description": "ACATS IN/OUT (Cash)."
-    },
-    {
-      "description": "ACATS IN/OUT (Securities)."
-    },
-    {
-      "description": "Cash deposit(+)."
-    },
-    {
-      "description": "Cash receipt(-)."
-    },
-    {
-      "description": "Cash withdrawal(-)."
-    },
-    {
-      "description": "Dividends."
-    },
-    {
-      "description": "Dividend (capital gain long term)."
-    },
-    {
-      "description": "Dividend (capital gain short term)."
-    },
-    {
-      "description": "Dividend fee."
-    },
-    {
-      "description": "Dividend adjusted (Foreign Tax Withheld)."
-    },
-    {
-      "description": "Dividend adjusted (NRA Withheld)."
-    },
-    {
-      "description": "Dividend return of capital."
-    },
-    {
-      "description": "Dividend adjusted (Tefra Withheld)."
-    },
-    {
-      "description": "Dividend (tax exempt)."
-    },
-    {
-      "description": "Interest (credit/margin)."
-    },
-    {
-      "description": "Interest adjusted (NRA Withheld)."
-    },
-    {
-      "description": "Interest adjusted (Tefra Withheld)."
-    },
-    {
-      "description": "Journal entry."
-    },
-    {
-      "description": "Journal entry (cash)."
-    },
-    {
-      "description": "Journal entry (stock)."
-    },
-    {
-      "description": "Merger/Acquisition."
-    },
-    {
-      "description": "Name change."
-    },
-    {
-      "description": "Option assignment."
-    },
-    {
-      "description": "Option expiration."
-    },
-    {
-      "description": "Option exercise."
-    },
-    {
-      "description": "Pass Thru Charge."
-    },
-    {
-      "description": "Pass Thru Rebate."
-    },
-    {
-      "description": "REG/TAF fees."
-    },
-    {
-      "description": "Reorg CA."
-    },
-    {
-      "description": "Symbol change."
-    },
-    {
-      "description": "Stock spinoff."
-    }
-  ]
-}
diff --git a/schema_json/endpoint/account_activities/enums/trade_activity_side.json b/schema_json/endpoint/account_activities/enums/trade_activity_side.json
deleted file mode 100644
index ddcd4869..00000000
--- a/schema_json/endpoint/account_activities/enums/trade_activity_side.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-activities/\">Account Activities</a>.",
-  "enum": [
-    "buy",
-    "sell"
-  ]
-}
diff --git a/schema_json/endpoint/account_activities/enums/trade_activity_type.json b/schema_json/endpoint/account_activities/enums/trade_activity_type.json
deleted file mode 100644
index 8b92e2b9..00000000
--- a/schema_json/endpoint/account_activities/enums/trade_activity_type.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-activities/\">Account Activities</a>.",
-  "enum": [
-    "fill",
-    "partial_fill"
-  ]
-}
diff --git a/schema_json/endpoint/account_activities/non_trade_activity.json b/schema_json/endpoint/account_activities/non_trade_activity.json
deleted file mode 100644
index c66a15af..00000000
--- a/schema_json/endpoint/account_activities/non_trade_activity.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-activities/\">Account Activities</a>.",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity"
-  },
-  "properties": {
-    "date": {
-      "existingJavaType": "java.lang.String",
-      "title": "The date on which the activity occurred or on which the transaction associated with the activity settled."
-    },
-    "net_amount": {
-      "existingJavaType": "java.lang.String",
-      "title": "The net amount of money (positive or negative) associated with the activity."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "The symbol of the security involved with the activity. Not present for all activity types."
-    },
-    "qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "For dividend activities, the number of shares that contributed to the payment. Not present for other activity types."
-    },
-    "per_share_amount": {
-      "existingJavaType": "java.lang.String",
-      "title": "For dividend activities, the average amount paid per share. Not present for other activity types."
-    },
-    "description": {
-      "existingJavaType": "java.lang.String",
-      "title": "Description field for transaction records (TRANS, CSR, CSD)."
-    },
-    "status": {
-      "existingJavaType": "java.lang.String",
-      "title": "The status."
-    }
-  }
-}
diff --git a/schema_json/endpoint/account_activities/trade_activity.json b/schema_json/endpoint/account_activities/trade_activity.json
deleted file mode 100644
index 0c6dd5f4..00000000
--- a/schema_json/endpoint/account_activities/trade_activity.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-activities/\">Account Activities</a>.",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity"
-  },
-  "properties": {
-    "cum_qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "The cumulative quantity of shares involved in the execution."
-    },
-    "leaves_qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "For partially_filled orders, the quantity of shares that are left to be filled."
-    },
-    "price": {
-      "existingJavaType": "java.lang.String",
-      "title": "The per-share price that the trade was executed at."
-    },
-    "qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "The number of shares involved in the trade execution."
-    },
-    "side": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivitySide",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivitySide}."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "The symbol of the security being traded."
-    },
-    "transaction_time": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "The time at which the execution occurred."
-    },
-    "order_id": {
-      "existingJavaType": "java.lang.String",
-      "title": "The id for the order that filled. Always in GUID format."
-    },
-    "type": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivityType",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivityType}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/account_configuration/account_configuration.json b/schema_json/endpoint/account_configuration/account_configuration.json
deleted file mode 100644
index 704ee4e1..00000000
--- a/schema_json/endpoint/account_configuration/account_configuration.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-configuration/\">Account Configuration</a>.",
-  "properties": {
-    "dtbp_check": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck}. Controls Day Trading Margin Call (DTMC) checks."
-    },
-    "trade_confirm_email": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail}. If {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail#NONE}, emails for order fills are not sent."
-    },
-    "suspend_trade": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "If true, new orders are blocked."
-    },
-    "no_shorting": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "If true, account becomes long-only mode."
-    }
-  }
-}
diff --git a/schema_json/endpoint/account_configuration/enums/dtbp_check.json b/schema_json/endpoint/account_configuration/enums/dtbp_check.json
deleted file mode 100644
index 75940d81..00000000
--- a/schema_json/endpoint/account_configuration/enums/dtbp_check.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "type": "string",
-  "javaName": "DTBPCheck",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-configuration/\">Account Configuration</a>.",
-  "enum": [
-    "both",
-    "entry",
-    "exit"
-  ]
-}
diff --git a/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json b/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json
deleted file mode 100644
index 8f28197c..00000000
--- a/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/account-configuration/\">Account Configuration</a>.",
-  "enum": [
-    "all",
-    "none"
-  ]
-}
diff --git a/schema_json/endpoint/asset/asset.json b/schema_json/endpoint/asset/asset.json
deleted file mode 100644
index b563bf4a..00000000
--- a/schema_json/endpoint/asset/asset.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/assets/\">Assets</a>.",
-  "properties": {
-    "id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Asset ID."
-    },
-    "class": {
-      "existingJavaType": "java.lang.String",
-      "title": "\"us_equity\""
-    },
-    "exchange": {
-      "existingJavaType": "java.lang.String",
-      "title": "AMEX, ARCA, BATS, NYSE, NASDAQ or NYSEARCA."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "The symbol."
-    },
-    "name": {
-      "existingJavaType": "java.lang.String",
-      "title": "The official name of the asset."
-    },
-    "status": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus}."
-    },
-    "tradable": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Asset is tradable on Alpaca or not."
-    },
-    "marginable": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Asset is marginable or not."
-    },
-    "shortable": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Asset is shortable or not."
-    },
-    "easy_to_borrow": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Asset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca)."
-    },
-    "fractionable": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Asset is fractionable or not."
-    }
-  }
-}
diff --git a/schema_json/endpoint/asset/enums/asset_status.json b/schema_json/endpoint/asset/enums/asset_status.json
deleted file mode 100644
index b77af48e..00000000
--- a/schema_json/endpoint/asset/enums/asset_status.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/assets/\">Assets</a>.",
-  "enum": [
-    "active",
-    "inactive"
-  ]
-}
diff --git a/schema_json/endpoint/calendar/calendar.json b/schema_json/endpoint/calendar/calendar.json
deleted file mode 100644
index 96207ded..00000000
--- a/schema_json/endpoint/calendar/calendar.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/calendar/\">Calendar</a>.",
-  "properties": {
-    "date": {
-      "existingJavaType": "java.time.LocalDate",
-      "title": "Date string in \"%Y-%m-%d\" format."
-    },
-    "open": {
-      "existingJavaType": "java.time.LocalTime",
-      "title": "The time the market opens at on this date in \"%H:%M\" format."
-    },
-    "close": {
-      "existingJavaType": "java.time.LocalTime",
-      "title": "The time the market closes at on this date in \"%H:%M\" format."
-    }
-  }
-}
diff --git a/schema_json/endpoint/clock/clock.json b/schema_json/endpoint/clock/clock.json
deleted file mode 100644
index 671d4a19..00000000
--- a/schema_json/endpoint/clock/clock.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/clock/\">Clock</a>.",
-  "properties": {
-    "timestamp": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Current timestamp."
-    },
-    "is_open": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "Whether or not the market is open."
-    },
-    "next_open": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Next market open timestamp."
-    },
-    "next_close": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Next market close timestamp."
-    }
-  }
-}
diff --git a/schema_json/endpoint/common/enums/sort_direction.json b/schema_json/endpoint/common/enums/sort_direction.json
deleted file mode 100644
index 01e5a4eb..00000000
--- a/schema_json/endpoint/common/enums/sort_direction.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "type": "string",
-  "title": "Defines enums for sort directions.",
-  "enum": [
-    "asc",
-    "desc"
-  ],
-  "javaEnums": [
-    {
-      "name": "ASCENDING"
-    },
-    {
-      "name": "DESCENDING"
-    }
-  ]
-}
diff --git a/schema_json/endpoint/market_data/historical/bar/bar.json b/schema_json/endpoint/market_data/historical/bar/bar.json
deleted file mode 100644
index b39bff53..00000000
--- a/schema_json/endpoint/market_data/historical/bar/bar.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "t": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Timestamp with nanosecond precision."
-    },
-    "o": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Open price."
-    },
-    "h": {
-      "existingJavaType": "java.lang.Double",
-      "title": "High price."
-    },
-    "l": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Low price."
-    },
-    "c": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Close price."
-    },
-    "v": {
-      "existingJavaType": "java.lang.Long",
-      "title": "Volume."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/bar/bars_response.json b/schema_json/endpoint/market_data/historical/bar/bars_response.json
deleted file mode 100644
index a876dcf8..00000000
--- a/schema_json/endpoint/market_data/historical/bar/bars_response.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "bars": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar>",
-      "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}s."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Symbol that was queried."
-    },
-    "next_page_token": {
-      "existingJavaType": "java.lang.String",
-      "title": "Token that can be used to query the next page."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json b/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json
deleted file mode 100644
index 89eb394f..00000000
--- a/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "enum": [
-    "1Min",
-    "1Hour",
-    "1Day"
-  ],
-  "javaEnums": [
-    {
-      "name": "ONE_MINUTE"
-    },
-    {
-      "name": "ONE_HOUR"
-    },
-    {
-      "name": "ONE_DAY"
-    }
-  ]
-}
diff --git a/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json b/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json
deleted file mode 100644
index 54477417..00000000
--- a/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Symbol that was queried."
-    },
-    "quote": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote",
-      "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/quote/quote.json b/schema_json/endpoint/market_data/historical/quote/quote.json
deleted file mode 100644
index 7a238f6e..00000000
--- a/schema_json/endpoint/market_data/historical/quote/quote.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "t": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Timestamp with nanosecond precision."
-    },
-    "ax": {
-      "existingJavaType": "java.lang.String",
-      "title": "Ask exchange."
-    },
-    "ap": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Ask price."
-    },
-    "as": {
-      "existingJavaType": "java.lang.Integer",
-      "title": "Ask size."
-    },
-    "bx": {
-      "existingJavaType": "java.lang.String",
-      "title": "Bid exchange."
-    },
-    "bp": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Bid price."
-    },
-    "bs": {
-      "existingJavaType": "java.lang.Integer",
-      "title": "Bid size."
-    },
-    "c": {
-      "existingJavaType": "java.util.ArrayList<String>",
-      "title": "The {@link java.util.ArrayList} of quote conditions."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/quote/quotes_response.json b/schema_json/endpoint/market_data/historical/quote/quotes_response.json
deleted file mode 100644
index 88c1286e..00000000
--- a/schema_json/endpoint/market_data/historical/quote/quotes_response.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "quotes": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote>",
-      "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}s."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Symbol that was queried."
-    },
-    "next_page_token": {
-      "existingJavaType": "java.lang.String",
-      "title": "Token that can be used to query the next page."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/snapshot/snapshot.json b/schema_json/endpoint/market_data/historical/snapshot/snapshot.json
deleted file mode 100644
index c2a0acf0..00000000
--- a/schema_json/endpoint/market_data/historical/snapshot/snapshot.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "latestTrade": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade",
-      "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}."
-    },
-    "latestQuote": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote",
-      "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}."
-    },
-    "minuteBar": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar",
-      "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}."
-    },
-    "dailyBar": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar",
-      "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}."
-    },
-    "prevDailyBar": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar",
-      "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json b/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json
deleted file mode 100644
index 3025a719..00000000
--- a/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Symbol that was queried."
-    },
-    "trade": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade",
-      "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/trade/trade.json b/schema_json/endpoint/market_data/historical/trade/trade.json
deleted file mode 100644
index c9303e7b..00000000
--- a/schema_json/endpoint/market_data/historical/trade/trade.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "t": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Timestamp with nanosecond precision."
-    },
-    "x": {
-      "existingJavaType": "java.lang.String",
-      "title": "Exchange where the trade happened."
-    },
-    "p": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Trade price."
-    },
-    "s": {
-      "existingJavaType": "java.lang.Integer",
-      "title": "Trade size."
-    },
-    "c": {
-      "existingJavaType": "java.util.ArrayList<String>",
-      "title": "The {@link java.util.ArrayList} of trade conditions."
-    },
-    "i": {
-      "existingJavaType": "java.lang.Long",
-      "title": "Trade ID."
-    },
-    "z": {
-      "existingJavaType": "java.lang.String",
-      "title": "Tape."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/historical/trade/trades_response.json b/schema_json/endpoint/market_data/historical/trade/trades_response.json
deleted file mode 100644
index 928f6f24..00000000
--- a/schema_json/endpoint/market_data/historical/trade/trades_response.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/\">Historical Data</a>.",
-  "properties": {
-    "trades": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade>",
-      "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}s."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Symbol that was queried."
-    },
-    "next_page_token": {
-      "existingJavaType": "java.lang.String",
-      "title": "Token that can be used to query the next page."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/bar/bar_message.json b/schema_json/endpoint/market_data/realtime/bar/bar_message.json
deleted file mode 100644
index cc6aa003..00000000
--- a/schema_json/endpoint/market_data/realtime/bar/bar_message.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "o": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "open",
-      "title": "Open price."
-    },
-    "h": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "high",
-      "title": "High price."
-    },
-    "l": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "low",
-      "title": "Low price."
-    },
-    "c": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "close",
-      "title": "Close price."
-    },
-    "v": {
-      "existingJavaType": "java.lang.Long",
-      "javaName": "volume",
-      "title": "Volume."
-    },
-    "t": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "javaName": "timestamp",
-      "title": "The timestamp."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json
deleted file mode 100644
index 6602ed55..00000000
--- a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "trades": {
-      "existingJavaType": "java.util.ArrayList<java.lang.String>",
-      "title": "The {@link java.util.ArrayList} of subscribed trades."
-    },
-    "quotes": {
-      "existingJavaType": "java.util.ArrayList<java.lang.String>",
-      "title": "The {@link java.util.ArrayList} of subscribed quotes."
-    },
-    "bars": {
-      "existingJavaType": "java.util.ArrayList<java.lang.String>",
-      "title": "The {@link java.util.ArrayList} of subscribed bars."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/control/success_message.json b/schema_json/endpoint/market_data/realtime/control/success_message.json
deleted file mode 100644
index 76ba8a8c..00000000
--- a/schema_json/endpoint/market_data/realtime/control/success_message.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "msg": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "message",
-      "title": "The success message."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/market_data_message.json b/schema_json/endpoint/market_data/realtime/market_data_message.json
deleted file mode 100644
index 3e43d789..00000000
--- a/schema_json/endpoint/market_data/realtime/market_data_message.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "T": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType",
-      "javaName": "messageType",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/realtime/quote/quote_message.json
deleted file mode 100644
index a8ff47b3..00000000
--- a/schema_json/endpoint/market_data/realtime/quote/quote_message.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "ax": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "askExchangeCode",
-      "title": "Ask exchange code."
-    },
-    "ap": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "askPrice",
-      "title": "Ask price."
-    },
-    "as": {
-      "existingJavaType": "java.lang.Integer",
-      "javaName": "askSize",
-      "title": "Ask size."
-    },
-    "bx": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "bidExchangeCode",
-      "title": "Bid exchange code."
-    },
-    "bp": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "bidPrice",
-      "title": "Bid price."
-    },
-    "bs": {
-      "existingJavaType": "java.lang.Integer",
-      "javaName": "bidSize",
-      "title": "Bid size."
-    },
-    "t": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "javaName": "timestamp",
-      "title": "Timestamp with nanosecond precision."
-    },
-    "c": {
-      "existingJavaType": "java.util.ArrayList<String>",
-      "javaName": "conditions",
-      "title": "The {@link java.util.ArrayList} of quote conditions."
-    },
-    "z": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "tape",
-      "title": "Tape."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/symbol_message.json b/schema_json/endpoint/market_data/realtime/symbol_message.json
deleted file mode 100644
index 961d15c0..00000000
--- a/schema_json/endpoint/market_data/realtime/symbol_message.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "S": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "symbol",
-      "title": "The symbol."
-    }
-  }
-}
diff --git a/schema_json/endpoint/market_data/realtime/trade/trade_message.json b/schema_json/endpoint/market_data/realtime/trade/trade_message.json
deleted file mode 100644
index 7d01e2b8..00000000
--- a/schema_json/endpoint/market_data/realtime/trade/trade_message.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
-  "properties": {
-    "i": {
-      "existingJavaType": "java.lang.Integer",
-      "javaName": "tradeID",
-      "title": "Trade ID."
-    },
-    "x": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "exchange",
-      "title": "Exchange code where the trade occurred."
-    },
-    "p": {
-      "existingJavaType": "java.lang.Double",
-      "javaName": "price",
-      "title": "Trade price."
-    },
-    "s": {
-      "existingJavaType": "java.lang.Integer",
-      "javaName": "size",
-      "title": "Trade size."
-    },
-    "t": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "javaName": "timestamp",
-      "title": "Timestamp with nanosecond precision."
-    },
-    "c": {
-      "existingJavaType": "java.util.ArrayList<String>",
-      "javaName": "conditions",
-      "title": "The {@link java.util.ArrayList} of trade conditions."
-    },
-    "z": {
-      "existingJavaType": "java.lang.String",
-      "javaName": "tape",
-      "title": "Tape."
-    }
-  }
-}
diff --git a/schema_json/endpoint/order/cancelled_order.json b/schema_json/endpoint/order/cancelled_order.json
deleted file mode 100644
index be79774c..00000000
--- a/schema_json/endpoint/order/cancelled_order.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "properties": {
-    "id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Order ID."
-    },
-    "status": {
-      "existingJavaType": "java.lang.Integer",
-      "title": "The HTTP status code of the cancelled order."
-    },
-    "body": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order",
-      "javaName": "order",
-      "title": "The cancelled {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/order/enums/current_order_status.json b/schema_json/endpoint/order/enums/current_order_status.json
deleted file mode 100644
index 25a5829f..00000000
--- a/schema_json/endpoint/order/enums/current_order_status.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "enum": [
-    "open",
-    "closed",
-    "all"
-  ]
-}
diff --git a/schema_json/endpoint/order/enums/order_class.json b/schema_json/endpoint/order/enums/order_class.json
deleted file mode 100644
index 6baaf86c..00000000
--- a/schema_json/endpoint/order/enums/order_class.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "enum": [
-    "simple",
-    "bracket",
-    "oco",
-    "oto"
-  ],
-  "javaEnums": [
-    {
-      "name": "SIMPLE"
-    },
-    {
-      "name": "BRACKET"
-    },
-    {
-      "name": "ONE_CANCELS_OTHER"
-    },
-    {
-      "name": "ONE_TRIGGERS_OTHER"
-    }
-  ]
-}
diff --git a/schema_json/endpoint/order/enums/order_side.json b/schema_json/endpoint/order/enums/order_side.json
deleted file mode 100644
index e8b6178f..00000000
--- a/schema_json/endpoint/order/enums/order_side.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "enum": [
-    "buy",
-    "sell"
-  ]
-}
diff --git a/schema_json/endpoint/order/enums/order_status.json b/schema_json/endpoint/order/enums/order_status.json
deleted file mode 100644
index 5174e120..00000000
--- a/schema_json/endpoint/order/enums/order_status.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "enum": [
-    "new",
-    "partially_filled",
-    "filled",
-    "done_for_day",
-    "canceled",
-    "expired",
-    "replaced",
-    "pending_cancel",
-    "pending_replace",
-    "accepted",
-    "pending_new",
-    "accepted_for_bidding",
-    "stopped",
-    "rejected",
-    "suspended",
-    "calculated",
-    "held"
-  ],
-  "javaEnums": [
-    {
-      "description": "The order has been received by Alpaca, and routed to exchanges for execution. This is the usual initial state of an order."
-    },
-    {
-      "description": "The order has been partially filled."
-    },
-    {
-      "description": "The order has been filled, and no further updates will occur for the order."
-    },
-    {
-      "description": "The order is done executing for the day, and will not receive further updates until the next trading day."
-    },
-    {
-      "description": "The order has been canceled, and no further updates will occur for the order. This can be either due to a cancel request by the user, or the order has been canceled by the exchanges due to its time-in-force."
-    },
-    {
-      "description": "The order has expired, and no further updates will occur for the order."
-    },
-    {
-      "description": "The order was replaced by another order, or was updated due to a market event such as corporate action."
-    },
-    {
-      "description": "The order is waiting to be canceled."
-    },
-    {
-      "description": "The order is waiting to be replaced by another order. The order will reject cancel request while in this state."
-    },
-    {
-      "description": "The order has been received by Alpaca, but hasn’t yet been routed to the execution venue. This could be seen often out side of trading session hours."
-    },
-    {
-      "description": "The order has been received by Alpaca, and routed to the exchanges, but has not yet been accepted for execution. This state only occurs on rare occasions."
-    },
-    {
-      "description": "The order has been received by exchanges, and is evaluated for pricing. This state only occurs on rare occasions."
-    },
-    {
-      "description": "The order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred. This state only occurs on rare occasions."
-    },
-    {
-      "description": "The order has been rejected, and no further updates will occur for the order. This state occurs on rare occasions and may occur based on various conditions decided by the exchanges."
-    },
-    {
-      "description": "The order has been suspended, and is not eligible for trading. This state only occurs on rare occasions."
-    },
-    {
-      "description": "The order has been completed for the day (either filled or done for day), but remaining settlement calculations are still pending. This state only occurs on rare occasions."
-    },
-    {
-      "description": "The order is part of a bracket leg that is waiting for the initial order to be filled."
-    }
-  ]
-}
diff --git a/schema_json/endpoint/order/enums/order_time_in_force.json b/schema_json/endpoint/order/enums/order_time_in_force.json
deleted file mode 100644
index fe468cd2..00000000
--- a/schema_json/endpoint/order/enums/order_time_in_force.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "enum": [
-    "day",
-    "gtc",
-    "opg",
-    "cls",
-    "ioc",
-    "fok"
-  ],
-  "javaEnums": [
-    {
-      "name": "DAY",
-      "description": "A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours."
-    },
-    {
-      "name": "GOOD_UNTIL_CANCELLED",
-      "description": "The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce(DNR) orders to opt out of such price adjustments."
-    },
-    {
-      "name": "OPG",
-      "description": "Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day’s opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange’s auction rules."
-    },
-    {
-      "name": "CLS",
-      "description": "Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day’s closing auction. Only available with API v2."
-    },
-    {
-      "name": "IMMEDIATE_OR_CANCEL",
-      "description": "An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Only available with API v2. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question."
-    },
-    {
-      "name": "FILL_OR_KILL",
-      "description": "A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled. Only available with API v2."
-    }
-  ]
-}
diff --git a/schema_json/endpoint/order/enums/order_type.json b/schema_json/endpoint/order/enums/order_type.json
deleted file mode 100644
index 32d93d2b..00000000
--- a/schema_json/endpoint/order/enums/order_type.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "enum": [
-    "market",
-    "limit",
-    "stop",
-    "stop_limit",
-    "trailing_stop"
-  ],
-  "javaEnums": [
-    {
-      "description": "A market order is a request to buy or sell a security at the currently available market price. It provides the most likely method of filling an order. Market orders fill nearly instantaneously."
-    },
-    {
-      "description": "A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify the limit price parameter when submitting your order."
-    },
-    {
-      "description": "A stop (market) order is an order to buy or sell a security when its price moves past a particular point, ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit orders with a limit price that is 4% higher than a stop price &lt; $50 (or 2.5% higher than a stop price &gt;= $50). Sell stop orders are not converted into stop limit orders."
-    },
-    {
-      "description": "A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order becomes a limit order to buy or sell at the limit price or better"
-    },
-    {
-      "description": "Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on the stock price movement. You request a single order with a dollar offset value or percentage value as the trail and the actual stop price for this order changes as the stock price moves in your favorable way, or stay at the last level otherwise. This way, you don’t need to monitor the price movement and keep sending replace requests to update the stop price close to the latest market movement."
-    }
-  ]
-}
diff --git a/schema_json/endpoint/order/order.json b/schema_json/endpoint/order/order.json
deleted file mode 100644
index 4af8dde8..00000000
--- a/schema_json/endpoint/order/order.json
+++ /dev/null
@@ -1,134 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
-  "properties": {
-    "id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Order ID."
-    },
-    "client_order_id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Client unique order ID."
-    },
-    "created_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Created at."
-    },
-    "updated_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Updated at."
-    },
-    "submitted_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Submitted at."
-    },
-    "filled_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Filled at."
-    },
-    "expired_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Expired at."
-    },
-    "canceled_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Canceled at."
-    },
-    "failed_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Failed at."
-    },
-    "replaced_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Replaced at."
-    },
-    "replaced_by": {
-      "existingJavaType": "java.lang.String",
-      "title": "The order ID that this order was replaced by."
-    },
-    "replaces": {
-      "existingJavaType": "java.lang.String",
-      "title": "The order ID that this order replaces."
-    },
-    "asset_id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Asset ID."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Asset symbol."
-    },
-    "asset_class": {
-      "existingJavaType": "java.lang.String",
-      "title": "Asset class."
-    },
-    "notional": {
-      "existingJavaType": "java.lang.String",
-      "title": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points."
-    },
-    "qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points."
-    },
-    "filled_qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "Filled quantity."
-    },
-    "filled_avg_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "Filled average price."
-    },
-    "order_class": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview."
-    },
-    "order_type": {
-      "existingJavaType": "java.lang.String",
-      "title": "(Deprecated with just type field below.)."
-    },
-    "type": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType}."
-    },
-    "side": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide}."
-    },
-    "time_in_force": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce}."
-    },
-    "limit_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "Limit price."
-    },
-    "stop_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "Stop price."
-    },
-    "status": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus}."
-    },
-    "extended_hours": {
-      "existingJavaType": "java.lang.Boolean",
-      "title": "If true, eligible for execution outside regular trading hours."
-    },
-    "legs": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.order.Order>",
-      "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.order.Order} entities associated with this order. Otherwise, null."
-    },
-    "trail_percent": {
-      "existingJavaType": "java.lang.String",
-      "title": "The percent value away from the high water mark for trailing stop orders."
-    },
-    "trail_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "The dollar value away from the high water mark for trailing stop orders."
-    },
-    "hwm": {
-      "existingJavaType": "java.lang.String",
-      "title": "High Water Mark - The highest (lowest) market price seen since the trailing stop order was submitted."
-    }
-  }
-}
diff --git a/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json b/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json
deleted file mode 100644
index b55c083d..00000000
--- a/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/portfolio-history/\">Portfolio History</a>.",
-  "enum": [
-    "D",
-    "W",
-    "M",
-    "A"
-  ],
-  "javaEnums": [
-    {
-      "name": "DAY"
-    },
-    {
-      "name": "WEEK"
-    },
-    {
-      "name": "MONTH"
-    },
-    {
-      "name": "YEAR"
-    }
-  ]
-}
diff --git a/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json b/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json
deleted file mode 100644
index 0fb614c7..00000000
--- a/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/portfolio-history/\">Portfolio History</a>.",
-  "enum": [
-    "1Min",
-    "5Min",
-    "15Min",
-    "1H",
-    "1D"
-  ],
-  "javaEnums": [
-    {
-      "name": "ONE_MIN"
-    },
-    {
-      "name": "FIVE_MINUTE"
-    },
-    {
-      "name": "FIFTEEN_MINUTE"
-    },
-    {
-      "name": "ONE_HOUR"
-    },
-    {
-      "name": "ONE_DAY"
-    }
-  ]
-}
diff --git a/schema_json/endpoint/portfolio_history/portfolio_history.json b/schema_json/endpoint/portfolio_history/portfolio_history.json
deleted file mode 100644
index f9f833b4..00000000
--- a/schema_json/endpoint/portfolio_history/portfolio_history.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/portfolio-history/\">Portfolio History</a>.",
-  "properties": {
-    "data_points": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint>",
-      "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint}s."
-    },
-    "base_value": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Basis in dollar of the profit loss calculation."
-    },
-    "timeframe": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame",
-      "title": "Time window size of each data element."
-    }
-  }
-}
diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json b/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json
deleted file mode 100644
index 8cafa239..00000000
--- a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/portfolio-history/\">Portfolio History</a>.",
-  "properties": {
-    "timestamp": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "{@link java.time.ZonedDateTime} of this data element."
-    },
-    "equity": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Equity value of the account in dollar amount as of the end of this time window."
-    },
-    "profit_loss": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Profit/loss in dollar from the base value."
-    },
-    "profit_loss_percent": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Profit/loss in percentage from the base value."
-    }
-  }
-}
diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_response.json b/schema_json/endpoint/portfolio_history/portfolio_history_response.json
deleted file mode 100644
index d43235f7..00000000
--- a/schema_json/endpoint/portfolio_history/portfolio_history_response.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/portfolio-history/\">Portfolio History</a>.",
-  "properties": {
-    "timestamp": {
-      "existingJavaType": "java.util.ArrayList<java.lang.Long>",
-      "title": "Time of each data element, left-labeled (the beginning of time window)."
-    },
-    "equity": {
-      "existingJavaType": "java.util.ArrayList<java.lang.Double>",
-      "title": "Equity value of the account in dollar amount as of the end of each time window."
-    },
-    "profit_loss": {
-      "existingJavaType": "java.util.ArrayList<java.lang.Double>",
-      "title": "Profit/loss in dollar from the base value."
-    },
-    "profit_loss_pct": {
-      "existingJavaType": "java.util.ArrayList<java.lang.Double>",
-      "title": "Profit/loss in percentage from the base value."
-    },
-    "base_value": {
-      "existingJavaType": "java.lang.Double",
-      "title": "Basis in dollar of the profit loss calculation."
-    },
-    "timeframe": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame",
-      "title": "Time window size of each data element."
-    }
-  }
-}
diff --git a/schema_json/endpoint/position/close_position_order.json b/schema_json/endpoint/position/close_position_order.json
deleted file mode 100644
index 5ce79575..00000000
--- a/schema_json/endpoint/position/close_position_order.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/positions/\">https://alpaca.markets/docs/api-documentation/api-v2/positions/</a>",
-  "properties": {
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "The symbol."
-    },
-    "status": {
-      "existingJavaType": "java.lang.Integer",
-      "title": "The HTTP status code of the position-closing order."
-    },
-    "body": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order",
-      "javaName": "order",
-      "title": "The position-closing {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/position/position.json b/schema_json/endpoint/position/position.json
deleted file mode 100644
index bee7e824..00000000
--- a/schema_json/endpoint/position/position.json
+++ /dev/null
@@ -1,70 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/positions/\">Positions</a>.",
-  "properties": {
-    "asset_id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Asset ID."
-    },
-    "symbol": {
-      "existingJavaType": "java.lang.String",
-      "title": "Symbol name of the asset."
-    },
-    "exchange": {
-      "existingJavaType": "java.lang.String",
-      "title": "Exchange name of the asset."
-    },
-    "asset_class": {
-      "existingJavaType": "java.lang.String",
-      "title": "Asset class name."
-    },
-    "avg_entry_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "Average entry price of the position."
-    },
-    "qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "The number of shares."
-    },
-    "side": {
-      "existingJavaType": "java.lang.String",
-      "title": "\"long\"."
-    },
-    "market_value": {
-      "existingJavaType": "java.lang.String",
-      "title": "Total dollar amount of the position."
-    },
-    "cost_basis": {
-      "existingJavaType": "java.lang.String",
-      "title": "Total cost basis in dollar."
-    },
-    "unrealized_pl": {
-      "existingJavaType": "java.lang.String",
-      "title": "Unrealized profit/loss in dollars."
-    },
-    "unrealized_plpc": {
-      "existingJavaType": "java.lang.String",
-      "title": "Unrealized profit/loss percent (by a factor of 1)."
-    },
-    "unrealized_intraday_pl": {
-      "existingJavaType": "java.lang.String",
-      "title": "Unrealized profit/loss in dollars for the day."
-    },
-    "unrealized_intraday_plpc": {
-      "existingJavaType": "java.lang.String",
-      "title": "Unrealized profit/loss percent (by a factor of 1)."
-    },
-    "current_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "Current asset price per share."
-    },
-    "lastday_price": {
-      "existingJavaType": "java.lang.String",
-      "title": "Last day’s asset price per share based on the closing value of the last trading day."
-    },
-    "change_today": {
-      "existingJavaType": "java.lang.String",
-      "title": "Percent change from last day price (by a factor of 1)."
-    }
-  }
-}
diff --git a/schema_json/endpoint/streaming/authorization/authorization_message.json b/schema_json/endpoint/streaming/authorization/authorization_message.json
deleted file mode 100644
index 5e266609..00000000
--- a/schema_json/endpoint/streaming/authorization/authorization_message.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage"
-  },
-  "properties": {
-    "data": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/streaming/enums/streaming_message_type.json b/schema_json/endpoint/streaming/enums/streaming_message_type.json
deleted file mode 100644
index 454ee643..00000000
--- a/schema_json/endpoint/streaming/enums/streaming_message_type.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "enum": [
-    "listening",
-    "authorization",
-    "trade_updates"
-  ]
-}
diff --git a/schema_json/endpoint/streaming/listening/listening_data.json b/schema_json/endpoint/streaming/listening/listening_data.json
deleted file mode 100644
index eb9d6eb9..00000000
--- a/schema_json/endpoint/streaming/listening/listening_data.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "properties": {
-    "streams": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType>",
-      "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType}s."
-    }
-  }
-}
diff --git a/schema_json/endpoint/streaming/listening/listening_message.json b/schema_json/endpoint/streaming/listening/listening_message.json
deleted file mode 100644
index 24413cb8..00000000
--- a/schema_json/endpoint/streaming/listening/listening_message.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage"
-  },
-  "properties": {
-    "data": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningData",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningData}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/streaming/streaming_message.json b/schema_json/endpoint/streaming/streaming_message.json
deleted file mode 100644
index c7799c71..00000000
--- a/schema_json/endpoint/streaming/streaming_message.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "properties": {
-    "stream": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/streaming/trade/trade_update.json b/schema_json/endpoint/streaming/trade/trade_update.json
deleted file mode 100644
index 46188863..00000000
--- a/schema_json/endpoint/streaming/trade/trade_update.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "properties": {
-    "event": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.trade.enums.TradeUpdateEvent",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.trade.enums.TradeUpdateEvent}."
-    },
-    "price": {
-      "existingJavaType": "java.lang.String",
-      "title": "The price."
-    },
-    "timestamp": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "The timestamp."
-    },
-    "position_qty": {
-      "existingJavaType": "java.lang.String",
-      "title": "The position quantity."
-    },
-    "order": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/streaming/trade/trade_update_message.json b/schema_json/endpoint/streaming/trade/trade_update_message.json
deleted file mode 100644
index 014d3d6f..00000000
--- a/schema_json/endpoint/streaming/trade/trade_update_message.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "type": "object",
-  "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage"
-  },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
-  "properties": {
-    "data": {
-      "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdate",
-      "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdate}."
-    }
-  }
-}
diff --git a/schema_json/endpoint/watchlist/watchlist.json b/schema_json/endpoint/watchlist/watchlist.json
deleted file mode 100644
index 0cfa52a0..00000000
--- a/schema_json/endpoint/watchlist/watchlist.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/watchlist/\">Watchlist</a>.",
-  "properties": {
-    "id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Watchlist ID."
-    },
-    "created_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Created at."
-    },
-    "updated_at": {
-      "existingJavaType": "java.time.ZonedDateTime",
-      "title": "Updated at."
-    },
-    "name": {
-      "existingJavaType": "java.lang.String",
-      "title": "User-defined watchlist name (up to 64 characters)."
-    },
-    "account_id": {
-      "existingJavaType": "java.lang.String",
-      "title": "Account ID."
-    },
-    "assets": {
-      "existingJavaType": "java.util.ArrayList<net.jacobpeterson.alpaca.model.endpoint.asset.Asset>",
-      "title": "The content of this watchlist, in the order as registered by the client."
-    }
-  }
-}
diff --git a/schema_json/properties/data_api_type.json b/schema_json/properties/data_api_type.json
deleted file mode 100644
index 3a782d77..00000000
--- a/schema_json/properties/data_api_type.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "type": "string",
-  "javaName": "DataAPIType",
-  "title": "Defines the data APIs to use with Alpaca.",
-  "enum": [
-    "iex",
-    "sip"
-  ]
-}
diff --git a/schema_json/properties/endpoint_api_type.json b/schema_json/properties/endpoint_api_type.json
deleted file mode 100644
index c0eacd4c..00000000
--- a/schema_json/properties/endpoint_api_type.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "type": "string",
-  "javaName": "EndpointAPIType",
-  "title": "Defines the API endpoints to use with Alpaca.",
-  "enum": [
-    "live",
-    "paper"
-  ]
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java
index 616e53e4..a4da83cf 100644
--- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java
+++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java
@@ -1,296 +1,360 @@
 package net.jacobpeterson.alpaca;
 
-import devcsrj.okhttp3.logging.HttpLoggingInterceptor;
-import net.jacobpeterson.alpaca.model.properties.DataAPIType;
-import net.jacobpeterson.alpaca.model.properties.EndpointAPIType;
-import net.jacobpeterson.alpaca.properties.AlpacaProperties;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.AssetsEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.CalendarEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.MarketDataEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.PortfolioHistoryEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.PositionsEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.WatchlistEndpoint;
-import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket;
-import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket;
-import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface;
-import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket;
-import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface;
+import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType;
+import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType;
+import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType;
+import net.jacobpeterson.alpaca.rest.broker.AlpacaBrokerAPI;
+import net.jacobpeterson.alpaca.rest.marketdata.AlpacaMarketDataAPI;
+import net.jacobpeterson.alpaca.rest.trader.AlpacaTraderAPI;
+import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocket;
+import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocketInterface;
+import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocket;
+import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocketInterface;
+import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocket;
+import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocketInterface;
+import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocket;
+import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocketInterface;
 import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
+import static net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType.SANDBOX;
+import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType.IEX;
+import static net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType.PAPER;
+import static okhttp3.logging.HttpLoggingInterceptor.Level.BODY;
 
 /**
- * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and {@link
- * AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your
- * application. Note that many methods inside the various {@link AlpacaEndpoint}s allow <code>null<code/> to be passed
- * in as a parameter if it is optional.
+ * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. If you are using the
+ * Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only
+ * need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca
+ * account, this class is optimized so that it can be instantiated quickly, especially when an existing
+ * {@link OkHttpClient} is given in the constructor. Additionally, all API endpoint instances are instantiated lazily.
+ * This class is thread-safe.
  *
- * @see <a href="https://docs.alpaca.markets/api-documentation/api-v2/">Alpaca API Documentation</a>
+ * @see <a href="https://docs.alpaca.markets">Alpaca Docs</a>
  */
 public class AlpacaAPI {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class);
 
-    private static final String VERSION_2_PATH_SEGMENT = "v2";
-
+    private final String traderKeyID;
+    private final String traderSecretKey;
+    private final String traderOAuthToken;
+    private final TraderAPIEndpointType traderAPIEndpointType;
+    private final MarketDataWebsocketSourceType marketDataWebsocketSourceType;
+    private final String brokerAPIKey;
+    private final String brokerAPISecret;
+    private final BrokerAPIEndpointType brokerAPIEndpointType;
     private final OkHttpClient okHttpClient;
-    private final AlpacaClient brokerClient;
-    private final AlpacaClient dataClient;
-    // Ordering of fields/methods below are analogous to the ordering in the Alpaca documentation
-    private final AccountEndpoint accountEndpoint;
-    private final MarketDataEndpoint marketDataEndpoint;
-    private final OrdersEndpoint ordersEndpoint;
-    private final PositionsEndpoint positionsEndpoint;
-    private final AssetsEndpoint assetsEndpoint;
-    private final WatchlistEndpoint watchlistEndpoint;
-    private final CalendarEndpoint calendarEndpoint;
-    private final ClockEndpoint clockEndpoint;
-    private final AccountConfigurationEndpoint accountConfigurationEndpoint;
-    private final AccountActivitiesEndpoint accountActivitiesEndpoint;
-    private final PortfolioHistoryEndpoint portfolioHistoryEndpoint;
-    private final StreamingWebsocket streamingWebsocket;
-    private final MarketDataWebsocket marketDataWebsocket;
+
+    private AlpacaTraderAPI trader;
+    private AlpacaMarketDataAPI marketData;
+    private AlpacaBrokerAPI broker;
+    private UpdatesWebsocket updatesWebsocket;
+    private StockMarketDataWebsocket stockMarketDataWebsocket;
+    private CryptoMarketDataWebsocket cryptoMarketDataWebsocket;
+    private NewsMarketDataWebsocket newsMarketDataWebsocket;
 
     /**
-     * Instantiates a new {@link AlpacaAPI} using properties specified in <code>alpaca.properties</code> file (or their
-     * associated defaults).
+     * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a
+     * single Alpaca account.
+     *
+     * @param traderKeyID                   the Trader key ID
+     * @param traderSecretKey               the Trader secret key
+     * @param traderAPIEndpointType         the {@link TraderAPIEndpointType}
+     * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType}
      */
-    public AlpacaAPI() {
-        this(AlpacaProperties.KEY_ID,
-                AlpacaProperties.SECRET_KEY,
-                AlpacaProperties.ENDPOINT_API_TYPE,
-                AlpacaProperties.DATA_API_TYPE);
+    public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType,
+            MarketDataWebsocketSourceType marketDataWebsocketSourceType) {
+        this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null,
+                null);
     }
 
     /**
-     * Instantiates a new {@link AlpacaAPI}.
+     * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a
+     * single Alpaca account and a custom {@link OkHttpClient} instance.
      *
-     * @param keyID  the key ID
-     * @param secret the secret
+     * @param traderKeyID                   the Trader key ID
+     * @param traderSecretKey               the Trader secret key
+     * @param traderAPIEndpointType         the {@link TraderAPIEndpointType}
+     * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType}
+     * @param okHttpClient                  an existing {@link OkHttpClient} or <code>null</code> to create a new
+     *                                      default instance
      */
-    public AlpacaAPI(String keyID, String secret) {
-        this(null, keyID, secret, null,
-                AlpacaProperties.ENDPOINT_API_TYPE,
-                AlpacaProperties.DATA_API_TYPE);
+    public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType,
+            MarketDataWebsocketSourceType marketDataWebsocketSourceType, OkHttpClient okHttpClient) {
+        this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null,
+                okHttpClient);
     }
 
     /**
-     * Instantiates a new {@link AlpacaAPI}.
+     * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on
+     * behalf of an Alpaca account.
      *
-     * @param keyID           the key ID
-     * @param secret          the secret
-     * @param endpointAPIType the {@link EndpointAPIType}
-     * @param dataAPIType     the {@link DataAPIType}
+     * @param traderOAuthToken      the Trader OAuth token
+     * @param traderAPIEndpointType the {@link TraderAPIEndpointType}
      */
-    public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) {
-        this(null, keyID, secret, null, endpointAPIType, dataAPIType);
+    public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType) {
+        this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, null);
     }
 
     /**
-     * Instantiates a new {@link AlpacaAPI}.
+     * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on
+     * behalf of an Alpaca account and a custom {@link OkHttpClient} instance.
+     *
+     * @param traderOAuthToken      the Trader OAuth token
+     * @param traderAPIEndpointType the {@link TraderAPIEndpointType}
+     * @param okHttpClient          an existing {@link OkHttpClient} or <code>null</code> to create a new default
+     *                              instance
+     */
+    public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) {
+        this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, okHttpClient);
+    }
+
+    /**
+     * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API.
+     *
+     * @param brokerAPIKey          the Broker API key
+     * @param brokerAPISecret       the Broker API secret
+     * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType}
+     */
+    public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType) {
+        this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, null);
+    }
+
+    /**
+     * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API and a custom
+     * {@link OkHttpClient} instance.
      *
-     * @param oAuthToken the OAuth token. Note that the Data API v2 does not work with OAuth tokens.
+     * @param brokerAPIKey          the Broker API key
+     * @param brokerAPISecret       the Broker API secret
+     * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType}
+     * @param okHttpClient          an existing {@link OkHttpClient} or <code>null</code> to create a new default
+     *                              instance
      */
-    public AlpacaAPI(String oAuthToken) {
-        this(null, null, null, oAuthToken,
-                AlpacaProperties.ENDPOINT_API_TYPE,
-                AlpacaProperties.DATA_API_TYPE);
+    public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType,
+            OkHttpClient okHttpClient) {
+        this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient);
     }
 
     /**
      * Instantiates a new {@link AlpacaAPI}.
      *
-     * @param okHttpClient    the {@link OkHttpClient} or <code>null</code> to create a default instance
-     * @param keyID           the key ID
-     * @param secretKey       the secret key
-     * @param oAuthToken      the OAuth token
-     * @param endpointAPIType the {@link EndpointAPIType}
-     * @param dataAPIType     the {@link DataAPIType}
+     * @param traderKeyID                   the Trader key ID
+     * @param traderSecretKey               the Trader secret key
+     * @param traderOAuthToken              the Trader OAuth token
+     * @param traderAPIEndpointType         the {@link TraderAPIEndpointType}
+     * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType}
+     * @param brokerAPIKey                  the Broker API key
+     * @param brokerAPISecret               the Broker API secret
+     * @param brokerAPIEndpointType         the {@link BrokerAPIEndpointType}
+     * @param okHttpClient                  an existing {@link OkHttpClient} or <code>null</code> to create a new
+     *                                      default instance
      */
-    public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken,
-            EndpointAPIType endpointAPIType, DataAPIType dataAPIType) {
-        checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null,
-                "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!",
-                keyID, secretKey, oAuthToken);
-        checkNotNull(endpointAPIType);
-        checkNotNull(dataAPIType);
-
-        // Create default 'okHttpClient'
+    public AlpacaAPI(String traderKeyID, String traderSecretKey,
+            String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType,
+            MarketDataWebsocketSourceType marketDataWebsocketSourceType,
+            String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType,
+            OkHttpClient okHttpClient) {
+        this.traderKeyID = traderKeyID;
+        this.traderSecretKey = traderSecretKey;
+        this.traderOAuthToken = traderOAuthToken;
+        this.traderAPIEndpointType = traderAPIEndpointType != null ?
+                traderAPIEndpointType : PAPER;
+        this.marketDataWebsocketSourceType = marketDataWebsocketSourceType != null ?
+                marketDataWebsocketSourceType : IEX;
+        this.brokerAPIKey = brokerAPIKey;
+        this.brokerAPISecret = brokerAPISecret;
+        this.brokerAPIEndpointType = brokerAPIEndpointType != null ?
+                brokerAPIEndpointType : SANDBOX;
+
+        // Create default OkHttpClient instance
         if (okHttpClient == null) {
-            OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
-                    .cache(null); // Ensure response caching is disabled
-
+            final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
             if (LOGGER.isDebugEnabled()) {
-                clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER));
+                final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(LOGGER::debug);
+                loggingInterceptor.setLevel(BODY);
+                clientBuilder.addInterceptor(loggingInterceptor);
             }
-
             okHttpClient = clientBuilder.build();
         }
-
         this.okHttpClient = okHttpClient;
-
-        String brokerHostSubdomain;
-        switch (endpointAPIType) {
-            case LIVE:
-                brokerHostSubdomain = "api";
-                break;
-            case PAPER:
-                brokerHostSubdomain = "paper-api";
-                break;
-            default:
-                throw new UnsupportedOperationException();
-        }
-
-        if (oAuthToken == null) {
-            brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey,
-                    brokerHostSubdomain, VERSION_2_PATH_SEGMENT);
-            dataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT);
-            marketDataWebsocket = new MarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey);
-        } else {
-            brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT);
-            dataClient = null;
-            marketDataWebsocket = null;
-        }
-
-        accountEndpoint = new AccountEndpoint(brokerClient);
-        marketDataEndpoint = dataClient == null ? null : new MarketDataEndpoint(dataClient);
-        ordersEndpoint = new OrdersEndpoint(brokerClient);
-        positionsEndpoint = new PositionsEndpoint(brokerClient);
-        assetsEndpoint = new AssetsEndpoint(brokerClient);
-        watchlistEndpoint = new WatchlistEndpoint(brokerClient);
-        calendarEndpoint = new CalendarEndpoint(brokerClient);
-        clockEndpoint = new ClockEndpoint(brokerClient);
-        accountConfigurationEndpoint = new AccountConfigurationEndpoint(brokerClient);
-        accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient);
-        portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient);
-        streamingWebsocket = new StreamingWebsocket(okHttpClient, brokerHostSubdomain, keyID, secretKey, oAuthToken);
     }
 
     /**
-     * @return {@link AccountEndpoint}
+     * Gets the {@link OkHttpClient}.
+     *
+     * @return the {@link OkHttpClient}
      */
-    public AccountEndpoint account() {
-        return accountEndpoint;
+    public OkHttpClient getOkHttpClient() {
+        return okHttpClient;
     }
 
     /**
-     * @return the {@link MarketDataEndpoint}
+     * Gets the {@link AlpacaTraderAPI}. Lazily instantiated.
+     *
+     * @return the {@link AlpacaTraderAPI}
      */
-    public MarketDataEndpoint marketData() {
-        return marketDataEndpoint;
+    public synchronized AlpacaTraderAPI trader() {
+        if (trader == null) {
+            trader = new AlpacaTraderAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType,
+                    okHttpClient);
+        }
+        return trader;
     }
 
     /**
-     * @return {@link OrdersEndpoint}
+     * Gets the {@link AlpacaMarketDataAPI}. Lazily instantiated.
+     *
+     * @return the {@link AlpacaMarketDataAPI}
      */
-    public OrdersEndpoint orders() {
-        return ordersEndpoint;
+    public synchronized AlpacaMarketDataAPI marketData() {
+        if (marketData == null) {
+            marketData = new AlpacaMarketDataAPI(traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret,
+                    okHttpClient);
+        }
+        return marketData;
     }
 
     /**
-     * @return {@link PositionsEndpoint}
+     * Gets the {@link AlpacaBrokerAPI}. Lazily instantiated.
+     *
+     * @return the {@link AlpacaBrokerAPI}
      */
-    public PositionsEndpoint positions() {
-        return positionsEndpoint;
+    public synchronized AlpacaBrokerAPI broker() {
+        if (broker == null) {
+            broker = new AlpacaBrokerAPI(brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient);
+        }
+        return broker;
     }
 
     /**
-     * @return {@link AssetsEndpoint}
+     * Gets the {@link UpdatesWebsocketInterface}. Lazily instantiated.
+     *
+     * @return the {@link UpdatesWebsocketInterface}
      */
-    public AssetsEndpoint assets() {
-        return assetsEndpoint;
+    public synchronized UpdatesWebsocketInterface updatesStream() {
+        if (updatesWebsocket == null) {
+            updatesWebsocket = new UpdatesWebsocket(okHttpClient, traderAPIEndpointType,
+                    traderKeyID, traderSecretKey, traderOAuthToken);
+        }
+        return updatesWebsocket;
     }
 
     /**
-     * @return {@link WatchlistEndpoint}
+     * Gets the {@link StockMarketDataWebsocketInterface}. Lazily instantiated.
+     *
+     * @return the {@link StockMarketDataWebsocketInterface}
      */
-    public WatchlistEndpoint watchlist() {
-        return watchlistEndpoint;
+    public synchronized StockMarketDataWebsocketInterface stockMarketDataStream() {
+        if (stockMarketDataWebsocket == null) {
+            stockMarketDataWebsocket = new StockMarketDataWebsocket(okHttpClient,
+                    traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, marketDataWebsocketSourceType);
+        }
+        return stockMarketDataWebsocket;
     }
 
     /**
-     * @return {@link CalendarEndpoint}
+     * Gets the {@link CryptoMarketDataWebsocketInterface}. Lazily instantiated.
+     *
+     * @return the {@link CryptoMarketDataWebsocketInterface}
      */
-    public CalendarEndpoint calendar() {
-        return calendarEndpoint;
+    public synchronized CryptoMarketDataWebsocketInterface cryptoMarketDataStream() {
+        if (cryptoMarketDataWebsocket == null) {
+            cryptoMarketDataWebsocket = new CryptoMarketDataWebsocket(okHttpClient,
+                    traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret);
+        }
+        return cryptoMarketDataWebsocket;
     }
 
     /**
-     * @return {@link ClockEndpoint}
+     * Gets the {@link NewsMarketDataWebsocketInterface}. Lazily instantiated.
+     *
+     * @return the {@link NewsMarketDataWebsocketInterface}
      */
-    public ClockEndpoint clock() {
-        return clockEndpoint;
+    public synchronized NewsMarketDataWebsocketInterface newsMarketDataStream() {
+        if (newsMarketDataWebsocket == null) {
+            newsMarketDataWebsocket = new NewsMarketDataWebsocket(okHttpClient,
+                    traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret);
+        }
+        return newsMarketDataWebsocket;
     }
 
     /**
-     * @return the {@link AccountConfigurationEndpoint}
+     * Creates a {@link Builder} for {@link AlpacaAPI}.
+     *
+     * @return the {@link Builder}
      */
-    public AccountConfigurationEndpoint accountConfiguration() {
-        return accountConfigurationEndpoint;
+    public static Builder builder() {
+        return new Builder();
     }
 
     /**
-     * @return the {@link AccountActivitiesEndpoint}
+     * A builder for {@link AlpacaAPI}
      */
-    public AccountActivitiesEndpoint accountActivities() {
-        return accountActivitiesEndpoint;
-    }
+    public static final class Builder {
+
+        private String traderKeyID;
+        private String traderSecretKey;
+        private String traderOAuthToken;
+        private TraderAPIEndpointType traderAPIEndpointType;
+        private MarketDataWebsocketSourceType marketDataWebsocketSourceType;
+        private String brokerAPIKey;
+        private String brokerAPISecret;
+        private BrokerAPIEndpointType brokerAPIEndpointType;
+        private OkHttpClient okHttpClient;
+
+        private Builder() {}
+
+        public Builder withTraderKeyID(String traderKeyID) {
+            this.traderKeyID = traderKeyID;
+            return this;
+        }
 
-    /**
-     * @return the {@link PortfolioHistoryEndpoint}
-     */
-    public PortfolioHistoryEndpoint portfolioHistory() {
-        return portfolioHistoryEndpoint;
-    }
+        public Builder withTraderSecretKey(String traderSecretKey) {
+            this.traderSecretKey = traderSecretKey;
+            return this;
+        }
 
-    /**
-     * @return the {@link StreamingWebsocketInterface}
-     */
-    public StreamingWebsocketInterface streaming() {
-        return streamingWebsocket;
-    }
+        public Builder withTraderOAuthToken(String traderOAuthToken) {
+            this.traderOAuthToken = traderOAuthToken;
+            return this;
+        }
 
-    /**
-     * @return the {@link MarketDataWebsocketInterface}
-     */
-    public MarketDataWebsocketInterface marketDataStreaming() {
-        return marketDataWebsocket;
-    }
+        public Builder withTraderAPIEndpointType(TraderAPIEndpointType traderAPIEndpointType) {
+            this.traderAPIEndpointType = traderAPIEndpointType;
+            return this;
+        }
 
-    /**
-     * Gets {@link #okHttpClient}.
-     *
-     * @return the {@link OkHttpClient}
-     */
-    public OkHttpClient getOkHttpClient() {
-        return okHttpClient;
-    }
+        public Builder withMarketDataWebsocketSourceType(MarketDataWebsocketSourceType marketDataWebsocketSourceType) {
+            this.marketDataWebsocketSourceType = marketDataWebsocketSourceType;
+            return this;
+        }
 
-    /**
-     * Gets {@link #brokerClient}.
-     *
-     * @return the broker {@link AlpacaClient}
-     */
-    public AlpacaClient getBrokerClient() {
-        return brokerClient;
-    }
+        public Builder withBrokerAPIKey(String brokerAPIKey) {
+            this.brokerAPIKey = brokerAPIKey;
+            return this;
+        }
 
-    /**
-     * Gets {@link #dataClient}.
-     *
-     * @return the data {@link AlpacaClient}
-     */
-    public AlpacaClient getDataClient() {
-        return dataClient;
+        public Builder withBrokerAPISecret(String brokerAPISecret) {
+            this.brokerAPISecret = brokerAPISecret;
+            return this;
+        }
+
+        public Builder withBrokerAPIEndpointType(BrokerAPIEndpointType brokerAPIEndpointType) {
+            this.brokerAPIEndpointType = brokerAPIEndpointType;
+            return this;
+        }
+
+        public Builder withOkHttpClient(OkHttpClient okHttpClient) {
+            this.okHttpClient = okHttpClient;
+            return this;
+        }
+
+        public AlpacaAPI build() {
+            return new AlpacaAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType,
+                    marketDataWebsocketSourceType, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient);
+        }
     }
 }
diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java
deleted file mode 100644
index 2b8d0b79..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package net.jacobpeterson.alpaca.properties;
-
-import net.jacobpeterson.alpaca.AlpacaAPI;
-import net.jacobpeterson.alpaca.model.properties.DataAPIType;
-import net.jacobpeterson.alpaca.model.properties.EndpointAPIType;
-
-import static net.jacobpeterson.alpaca.util.properties.PropertyUtil.getProperty;
-
-/**
- * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}.
- */
-public class AlpacaProperties {
-
-    private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties";
-    private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties";
-
-    private static final String KEY_ID_KEY = "key_id";
-    /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */
-    public static final String KEY_ID = getProperty(
-            ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE,
-            KEY_ID_KEY);
-
-    private static final String SECRET_KEY_KEY = "secret_key";
-    /** The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */
-    public static final String SECRET_KEY = getProperty(
-            ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE,
-            SECRET_KEY_KEY);
-
-    private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type";
-    /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */
-    public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty(
-            ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE,
-            ENDPOINT_API_TYPE_KEY));
-
-    private static final String DATA_API_TYPE_KEY = "data_api_type";
-    /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */
-    public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty(
-            ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE,
-            DATA_API_TYPE_KEY));
-
-    private static final String USER_AGENT_KEY = "user_agent";
-    /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */
-    public static final String USER_AGENT = getProperty(
-            ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE,
-            USER_AGENT_KEY);
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java
deleted file mode 100644
index 90e7265a..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java
+++ /dev/null
@@ -1,242 +0,0 @@
-package net.jacobpeterson.alpaca.rest;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParser;
-import net.jacobpeterson.alpaca.properties.AlpacaProperties;
-import okhttp3.Headers;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.lang.reflect.Type;
-import java.util.function.Predicate;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON;
-
-/**
- * {@link AlpacaClient} represents an HTTP RestAPI client for Alpaca.
- */
-public class AlpacaClient {
-
-    private final OkHttpClient okHttpClient;
-    private final HttpUrl baseURL;
-    private final Headers requestHeaders;
-
-    /**
-     * Instantiates a new {@link AlpacaClient}.
-     *
-     * @param okHttpClient       the {@link OkHttpClient}
-     * @param keyID              the key ID
-     * @param secretKey          the secret key
-     * @param alpacaSubdomain    the Alpaca subdomain
-     * @param versionPathSegment the version path segment e.g. "v2"
-     */
-    public AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String alpacaSubdomain,
-            String versionPathSegment) {
-        this(okHttpClient, keyID, secretKey, null, alpacaSubdomain, versionPathSegment);
-    }
-
-    /**
-     * Instantiates a new {@link AlpacaClient}.
-     *
-     * @param okHttpClient       the {@link OkHttpClient}
-     * @param oAuthToken         the OAuth token
-     * @param alpacaSubdomain    the Alpaca subdomain
-     * @param versionPathSegment the version path segment e.g. "v2"
-     */
-    public AlpacaClient(OkHttpClient okHttpClient, String oAuthToken, String alpacaSubdomain,
-            String versionPathSegment) {
-        this(okHttpClient, null, null, oAuthToken, alpacaSubdomain, versionPathSegment);
-    }
-
-    /**
-     * Instantiates a new {@link AlpacaClient}.
-     *
-     * @param okHttpClient       the {@link OkHttpClient}
-     * @param keyID              the key ID
-     * @param secretKey          the secret key
-     * @param oAuthToken         the OAuth token
-     * @param alpacaSubdomain    the Alpaca subdomain
-     * @param versionPathSegment the version path segment e.g. "v2"
-     */
-    protected AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken,
-            String alpacaSubdomain, String versionPathSegment) {
-        checkNotNull(okHttpClient);
-        checkNotNull(alpacaSubdomain);
-        checkNotNull(versionPathSegment);
-
-        this.okHttpClient = okHttpClient;
-
-        baseURL = new HttpUrl.Builder()
-                .scheme("https")
-                .host(alpacaSubdomain + ".alpaca.markets")
-                .addPathSegment(versionPathSegment)
-                .build();
-
-        Headers.Builder requestHeadersBuilder = new Headers.Builder();
-        requestHeadersBuilder.add("User-Agent", AlpacaProperties.USER_AGENT);
-        if (oAuthToken != null) {
-            requestHeadersBuilder.add("Authorization", "Bearer " + oAuthToken);
-        } else {
-            requestHeadersBuilder
-                    .add("APCA-API-KEY-ID", keyID)
-                    .add("APCA-API-SECRET-KEY", secretKey);
-        }
-        requestHeaders = requestHeadersBuilder.build();
-    }
-
-    /**
-     * Gets a new {@link okhttp3.HttpUrl.Builder}.
-     *
-     * @return a {@link okhttp3.HttpUrl.Builder}
-     */
-    public HttpUrl.Builder urlBuilder() {
-        return baseURL.newBuilder();
-    }
-
-    /**
-     * Gets a new {@link okhttp3.Request.Builder} with {@link #requestHeaders}.
-     *
-     * @param httpUrl the {@link okhttp3.HttpUrl}
-     *
-     * @return a {@link okhttp3.Request.Builder}
-     */
-    public Request.Builder requestBuilder(HttpUrl httpUrl) {
-        return new Request.Builder().headers(requestHeaders).url(httpUrl);
-    }
-
-    /**
-     * Calls {@link #requestObject(Request, Predicate, Type)} with a success code of <code>200</code>.
-     */
-    public <T> T requestObject(Request request, Type type) throws AlpacaClientException {
-        return requestObject(request, (code) -> code == 200, type);
-    }
-
-    /**
-     * Requests an object given a {@link Request} that is deserialized via {@link Gson#fromJson(Reader, Type)}.
-     *
-     * @param <T>           the type of object
-     * @param request       the {@link Request}
-     * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link
-     *                      Predicate#test(Object)} returns false
-     * @param type          the object {@link Type}
-     *
-     * @return the requested object
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public <T> T requestObject(Request request, Predicate<Integer> isSuccessCode, Type type)
-            throws AlpacaClientException {
-        return GSON.fromJson(requestJSON(request, isSuccessCode), type);
-    }
-
-    /**
-     * Calls {@link #requestJSON(Request, Predicate)} with a success code of <code>200</code>.
-     */
-    public JsonElement requestJSON(Request request) throws AlpacaClientException {
-        return requestJSON(request, (code) -> code == 200);
-    }
-
-    /**
-     * Requests a {@link JsonElement} given a {@link Request}.
-     *
-     * @param request       the {@link Request}
-     * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link
-     *                      Predicate#test(Object)} returns false
-     *
-     * @return the {@link JsonElement}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public JsonElement requestJSON(Request request, Predicate<Integer> isSuccessCode) throws AlpacaClientException {
-        try (Response response = request(request, isSuccessCode)) {
-            try (ResponseBody responseBody = response.body()) {
-                // Throw exception if 'responseBody' is ever null since we never want to use cachedResponse or
-                // other types of responses.
-                checkState(responseBody != null);
-
-                try (Reader charStream = responseBody.charStream()) {
-                    return JsonParser.parseReader(charStream);
-                } catch (IOException ioException) {
-                    throw new AlpacaClientException(ioException);
-                }
-            }
-        }
-    }
-
-    /**
-     * Sends a {@link Request} and ignores any {@link ResponseBody}.
-     *
-     * @param request       the {@link Request}
-     * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link
-     *                      Predicate#test(Object)} returns false
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public void requestVoid(Request request, Predicate<Integer> isSuccessCode) throws AlpacaClientException {
-        // close() will throw an exception if cachedResponse or others were used which is fine
-        // since we never want to use those types of responses.
-        request(request, isSuccessCode).close();
-    }
-
-    /**
-     * Sends a {@link Request} and returns a {@link Response}.
-     *
-     * @param request       the {@link Request}
-     * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link
-     *                      Predicate#test(Object)} returns false
-     *
-     * @return the {@link Response}. <strong>Be sure to call {@link Response#close()} after use.</strong>
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Response request(Request request, Predicate<Integer> isSuccessCode) throws AlpacaClientException {
-        try {
-            Response response = executeRequest(request);
-            if (!isSuccessCode.test(response.code())) {
-                throw new AlpacaClientException(response);
-            }
-            return response;
-        } catch (IOException ioException) {
-            throw new AlpacaClientException(ioException);
-        }
-    }
-
-    /**
-     * Executes a {@link Request}.
-     *
-     * @param request the {@link Request}
-     *
-     * @return the {@link Response}. <strong>Be sure to call {@link Response#close()} after use.</strong>
-     *
-     * @throws IOException thrown for {@link IOException}s
-     */
-    public Response executeRequest(Request request) throws IOException {
-        return okHttpClient.newCall(request).execute();
-    }
-
-    /**
-     * Gets {@link #baseURL}.
-     *
-     * @return the base {@link HttpUrl}
-     */
-    public HttpUrl getBaseURL() {
-        return baseURL;
-    }
-
-    /**
-     * Gets {@link #requestHeaders}.
-     *
-     * @return the request {@link Headers}
-     */
-    public Headers getRequestHeaders() {
-        return requestHeaders;
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java
deleted file mode 100644
index 40ef1d6c..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package net.jacobpeterson.alpaca.rest;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-
-public class AlpacaClientException extends Exception {
-
-    private static final String CODE_KEY = "code";
-    private static final String MESSAGE_KEY = "message";
-
-    private String responseBody;
-    private Integer responseStatusCode;
-    private String responseStatusMessage;
-    private Integer apiResponseCode;
-    private String apiResponseMessage;
-
-    /**
-     * Instantiates a new {@link AlpacaClientException}.
-     *
-     * @param message the message
-     */
-    public AlpacaClientException(String message) {
-        super(message);
-    }
-
-    /**
-     * Instantiates a new {@link AlpacaClientException}.
-     *
-     * @param cause the cause {@link Throwable}
-     */
-    public AlpacaClientException(Throwable cause) {
-        super(cause);
-    }
-
-    /**
-     * Instantiates a new {@link AlpacaClientException}.
-     *
-     * @param message the message
-     * @param cause   the cause {@link Throwable}
-     */
-    public AlpacaClientException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    /**
-     * Instantiates a new {@link AlpacaClientException}.
-     *
-     * @param response the {@link Response} containing an API response code and message JSON
-     */
-    protected AlpacaClientException(Response response) {
-        responseStatusCode = response.code();
-        responseStatusMessage = response.message();
-
-        try (ResponseBody clientResponseBody = response.body()) {
-            if (clientResponseBody != null) {
-                this.responseBody = clientResponseBody.string();
-            }
-        } catch (Exception ignored) {}
-    }
-
-    @Override
-    public String getMessage() {
-        if (responseBody == null) {
-            return super.getMessage();
-        } else {
-            try {
-                return parseAPIErrorResponse();
-            } catch (Exception ignored) {
-                return super.getMessage();
-            }
-        }
-    }
-
-    /**
-     * Parses an API error response of the following format:
-     * <pre>
-     *  {
-     *     "code": 505,
-     *     "message": "Error message"
-     *  }
-     * </pre>
-     * and sets {@link #apiResponseCode} and {@link #apiResponseMessage} accordingly.
-     *
-     * @return a formatted message of the error response
-     */
-    private String parseAPIErrorResponse() {
-        JsonElement responseJsonElement = JsonParser.parseString(responseBody);
-
-        if (responseJsonElement instanceof JsonObject) {
-            JsonObject responseJsonObject = responseJsonElement.getAsJsonObject();
-
-            if (responseJsonObject.has(CODE_KEY)) {
-                apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt();
-            }
-
-            if (responseJsonObject.has(MESSAGE_KEY)) {
-                apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString();
-            }
-        }
-
-        // Just use the response JSON if the message could not be parsed.
-        if (apiResponseMessage == null) {
-            apiResponseMessage = responseJsonElement.toString();
-        }
-
-        // Build message
-        StringBuilder messageBuilder = new StringBuilder();
-        messageBuilder.append("Status Code: ").append(responseStatusCode);
-        messageBuilder.append(", Status Message: \"").append(responseStatusMessage).append("\"");
-
-        if (apiResponseCode != null) {
-            messageBuilder.append(", API Response Code: ").append(apiResponseCode);
-        }
-
-        if (apiResponseMessage != null) {
-            messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\"");
-        }
-
-        return messageBuilder.toString();
-    }
-
-    /**
-     * Gets the {@link #responseStatusCode}.
-     *
-     * @return an {@link Integer}
-     */
-    public Integer getResponseStatusCode() {
-        return responseStatusCode;
-    }
-
-    /**
-     * Gets the {@link #responseStatusMessage}.
-     *
-     * @return a {@link String}
-     */
-    public String getResponseStatusMessage() {
-        return responseStatusMessage;
-    }
-
-    /**
-     * Gets the {@link #apiResponseCode}.
-     *
-     * @return an {@link Integer}
-     */
-    public Integer getAPIResponseCode() {
-        return apiResponseCode;
-    }
-
-    /**
-     * Gets the {@link #apiResponseMessage}.
-     *
-     * @return a {@link String}
-     */
-    public String getAPIResponseMessage() {
-        return apiResponseMessage;
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java
new file mode 100644
index 00000000..cfbd3e75
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java
@@ -0,0 +1,338 @@
+package net.jacobpeterson.alpaca.rest.broker;
+
+import com.google.gson.JsonElement;
+import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType;
+import net.jacobpeterson.alpaca.openapi.broker.ApiClient;
+import net.jacobpeterson.alpaca.openapi.broker.api.AccountsApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.AssetsApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.CalendarApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.ClockApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.CorporateActionsApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.CountryInfoApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.DocumentsApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.FundingApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.FundingWalletsApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.JournalsApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.KycApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.LogosApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.OAuthApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.RebalancingApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi;
+import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi;
+import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLegacyNote;
+import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLiquidation;
+import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionTransactionCancel;
+import net.jacobpeterson.alpaca.openapi.broker.model.JNLC;
+import net.jacobpeterson.alpaca.openapi.broker.model.JNLS;
+import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivity;
+import net.jacobpeterson.alpaca.openapi.broker.model.TradeActivity;
+import net.jacobpeterson.alpaca.rest.broker.events.EventsApiSSE;
+import okhttp3.OkHttpClient;
+
+import java.util.function.BiFunction;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey;
+
+/**
+ * {@link AlpacaBrokerAPI} is the class used to interface with the Alpaca Broker API endpoints. This class is
+ * thread-safe.
+ */
+public class AlpacaBrokerAPI {
+
+    // Set validation predicates for 'anyOf' and 'oneOf' models
+    static {
+        // 'AccountsApi' models
+        TradeActivity.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type");
+        NonTradeActivity.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type");
+
+        // 'JournalsApi' models
+        final BiFunction<Boolean, JsonElement, Boolean> transferPredicate = (jnlc, jsonElement) -> {
+            final String entryType = jsonElement.getAsJsonObject().get("entry_type").getAsString();
+            return jnlc ? entryType.equals("JNLC") : entryType.equals("JNLS");
+        };
+        JNLS.isValid = jsonElement -> transferPredicate.apply(true, jsonElement);
+        JNLC.isValid = jsonElement -> transferPredicate.apply(false, jsonElement);
+
+        // 'EventsApi' models
+        AdminActionLegacyNote.isValid = jsonElement -> jsonElement.getAsJsonObject()
+                .get("type").getAsString().contains("note_admin_event");
+        AdminActionLiquidation.isValid = jsonElement -> jsonElement.getAsJsonObject()
+                .get("type").getAsString().equals("liquidation_admin_event");
+        AdminActionTransactionCancel.isValid = jsonElement -> jsonElement.getAsJsonObject()
+                .get("type").getAsString().equals("transaction_cancel_admin_event");
+    }
+
+    private final ApiClient apiClient;
+    private AccountsApi accounts;
+    private AssetsApi assets;
+    private CalendarApi calendar;
+    private ClockApi clock;
+    private CorporateActionsApi corporateActions;
+    private CountryInfoApi countryInfo;
+    private DocumentsApi documents;
+    private EventsApiSSE events;
+    private FundingApi funding;
+    private FundingWalletsApi fundingWallets;
+    private JournalsApi journals;
+    private KycApi kyc;
+    private LogosApi logos;
+    private OAuthApi oAuth;
+    private RebalancingApi rebalancing;
+    private ReportingApi reporting;
+    private TradingApi trading;
+    private WatchlistApi watchlist;
+
+    /**
+     * Instantiates a new {@link AlpacaBrokerAPI}.
+     *
+     * @param brokerAPIKey          the Broker API key
+     * @param brokerAPISecret       the Broker API secret
+     * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType}
+     * @param okHttpClient          an existing {@link OkHttpClient} or <code>null</code> to create a new default
+     *                              instance
+     */
+    @SuppressWarnings("UnnecessaryDefault")
+    public AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType,
+            OkHttpClient okHttpClient) {
+        checkNotNull(brokerAPIKey);
+        checkNotNull(brokerAPISecret);
+        checkNotNull(brokerAPIEndpointType);
+        checkNotNull(okHttpClient);
+
+        apiClient = new ApiClient(okHttpClient);
+        apiClient.setServerIndex(switch (brokerAPIEndpointType) {
+            case SANDBOX -> 0;
+            case PRODUCTION -> 1;
+            default -> throw new UnsupportedOperationException();
+        });
+        apiClient.addDefaultHeader("Authorization", "Basic " +
+                createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret));
+    }
+
+    /**
+     * Gets the internal {@link ApiClient}.
+     *
+     * @return the {@link ApiClient}
+     */
+    public ApiClient getInternalAPIClient() {
+        return apiClient;
+    }
+
+    /**
+     * Gets the {@link AccountsApi}. Lazily instantiated.
+     *
+     * @return the {@link AccountsApi}
+     */
+    public synchronized AccountsApi accounts() {
+        if (accounts == null) {
+            accounts = new AccountsApi(apiClient);
+        }
+        return accounts;
+    }
+
+    /**
+     * Gets the {@link AssetsApi}. Lazily instantiated.
+     *
+     * @return the {@link AssetsApi}
+     */
+    public synchronized AssetsApi assets() {
+        if (assets == null) {
+            assets = new AssetsApi(apiClient);
+        }
+        return assets;
+    }
+
+    /**
+     * Gets the {@link CalendarApi}. Lazily instantiated.
+     *
+     * @return the {@link CalendarApi}
+     */
+    public synchronized CalendarApi calendar() {
+        if (calendar == null) {
+            calendar = new CalendarApi(apiClient);
+        }
+        return calendar;
+    }
+
+    /**
+     * Gets the {@link ClockApi}. Lazily instantiated.
+     *
+     * @return the {@link ClockApi}
+     */
+    public synchronized ClockApi clock() {
+        if (clock == null) {
+            clock = new ClockApi(apiClient);
+        }
+        return clock;
+    }
+
+    /**
+     * Gets the {@link CorporateActionsApi}. Lazily instantiated.
+     *
+     * @return the {@link CorporateActionsApi}
+     */
+    public synchronized CorporateActionsApi corporateActions() {
+        if (corporateActions == null) {
+            corporateActions = new CorporateActionsApi(apiClient);
+        }
+        return corporateActions;
+    }
+
+    /**
+     * Gets the {@link CountryInfoApi}. Lazily instantiated.
+     *
+     * @return the {@link CountryInfoApi}
+     */
+    public synchronized CountryInfoApi countryInfo() {
+        if (countryInfo == null) {
+            countryInfo = new CountryInfoApi(apiClient);
+        }
+        return countryInfo;
+    }
+
+    /**
+     * Gets the {@link DocumentsApi}. Lazily instantiated.
+     *
+     * @return the {@link DocumentsApi}
+     */
+    public synchronized DocumentsApi documents() {
+        if (documents == null) {
+            documents = new DocumentsApi(apiClient);
+        }
+        return documents;
+    }
+
+    /**
+     * Gets the {@link EventsApiSSE}. Lazily instantiated.
+     *
+     * @return the {@link EventsApiSSE}
+     */
+    public synchronized EventsApiSSE events() {
+        if (events == null) {
+            events = new EventsApiSSE(apiClient);
+        }
+        return events;
+    }
+
+    /**
+     * Gets the {@link FundingApi}. Lazily instantiated.
+     *
+     * @return the {@link FundingApi}
+     */
+    public synchronized FundingApi funding() {
+        if (funding == null) {
+            funding = new FundingApi(apiClient);
+        }
+        return funding;
+    }
+
+    /**
+     * Gets the {@link FundingWalletsApi}. Lazily instantiated.
+     *
+     * @return the {@link FundingWalletsApi}
+     */
+    public synchronized FundingWalletsApi fundingWallets() {
+        if (fundingWallets == null) {
+            fundingWallets = new FundingWalletsApi(apiClient);
+        }
+        return fundingWallets;
+    }
+
+    /**
+     * Gets the {@link JournalsApi}. Lazily instantiated.
+     *
+     * @return the {@link JournalsApi}
+     */
+    public synchronized JournalsApi journals() {
+        if (journals == null) {
+            journals = new JournalsApi(apiClient);
+        }
+        return journals;
+    }
+
+    /**
+     * Gets the {@link KycApi}. Lazily instantiated.
+     *
+     * @return the {@link KycApi}
+     */
+    public synchronized KycApi kyc() {
+        if (kyc == null) {
+            kyc = new KycApi(apiClient);
+        }
+        return kyc;
+    }
+
+    /**
+     * Gets the {@link LogosApi}. Lazily instantiated.
+     *
+     * @return the {@link LogosApi}
+     */
+    public synchronized LogosApi logos() {
+        if (logos == null) {
+            logos = new LogosApi(apiClient);
+        }
+        return logos;
+    }
+
+    /**
+     * Gets the {@link OAuthApi}. Lazily instantiated.
+     *
+     * @return the {@link OAuthApi}
+     */
+    public synchronized OAuthApi oAuth() {
+        if (oAuth == null) {
+            oAuth = new OAuthApi(apiClient);
+        }
+        return oAuth;
+    }
+
+    /**
+     * Gets the {@link RebalancingApi}. Lazily instantiated.
+     *
+     * @return the {@link RebalancingApi}
+     */
+    public synchronized RebalancingApi rebalancing() {
+        if (rebalancing == null) {
+            rebalancing = new RebalancingApi(apiClient);
+        }
+        return rebalancing;
+    }
+
+    /**
+     * Gets the {@link ReportingApi}. Lazily instantiated.
+     *
+     * @return the {@link ReportingApi}
+     */
+    public synchronized ReportingApi reporting() {
+        if (reporting == null) {
+            reporting = new ReportingApi(apiClient);
+        }
+        return reporting;
+    }
+
+    /**
+     * Gets the {@link TradingApi}. Lazily instantiated.
+     *
+     * @return the {@link TradingApi}
+     */
+    public synchronized TradingApi trading() {
+        if (trading == null) {
+            trading = new TradingApi(apiClient);
+        }
+        return trading;
+    }
+
+    /**
+     * Gets the {@link WatchlistApi}. Lazily instantiated.
+     *
+     * @return the {@link WatchlistApi}
+     */
+    public synchronized WatchlistApi watchlist() {
+        if (watchlist == null) {
+            watchlist = new WatchlistApi(apiClient);
+        }
+        return watchlist;
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java
new file mode 100644
index 00000000..2c18b5ff
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java
@@ -0,0 +1,190 @@
+package net.jacobpeterson.alpaca.rest.broker.events;
+
+import com.google.gson.reflect.TypeToken;
+import net.jacobpeterson.alpaca.openapi.broker.ApiClient;
+import net.jacobpeterson.alpaca.openapi.broker.ApiException;
+import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi;
+import net.jacobpeterson.alpaca.openapi.broker.model.AccountStatusEvent;
+import net.jacobpeterson.alpaca.openapi.broker.model.JournalStatusEvent;
+import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivityEvent;
+import net.jacobpeterson.alpaca.openapi.broker.model.SubscribeToAdminActionSSE200ResponseInner;
+import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEvent;
+import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEventV2;
+import net.jacobpeterson.alpaca.openapi.broker.model.TransferStatusEvent;
+import net.jacobpeterson.alpaca.util.sse.SSEListener;
+import net.jacobpeterson.alpaca.util.sse.SSERequest;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.sse.EventSource;
+import okhttp3.sse.EventSourceListener;
+import okhttp3.sse.EventSources;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Type;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.util.UUID;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static net.jacobpeterson.alpaca.openapi.broker.JSON.getGson;
+
+/**
+ * {@link EventsApiSSE} add SSE support to {@link EventsApi}.
+ */
+public class EventsApiSSE {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(EventsApiSSE.class);
+
+    private final EventsApi eventsAPI;
+    private final EventSource.Factory eventSourceFactory;
+
+    /**
+     * Instantiates a new {@link EventsApiSSE}.
+     *
+     * @param apiClient the api client
+     */
+    public EventsApiSSE(ApiClient apiClient) {
+        eventsAPI = new EventsApi(apiClient);
+        eventSourceFactory = EventSources.createFactory(apiClient.getHttpClient().newBuilder() // Shallow clone
+                .readTimeout(0, SECONDS)
+                .writeTimeout(0, SECONDS)
+                .build());
+    }
+
+    /**
+     * See {@link EventsApi#getV1EventsNta(String, String, String, Integer, Integer, String, String, Boolean, UUID)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToNonTradingActivitiesEvents(String id, String since, String until, Integer sinceId,
+            Integer untilId, String sinceUlid, String untilUlid, Boolean includePreprocessing, UUID groupId,
+            SSEListener<Object> sseListener) throws ApiException {
+        final Request request = eventsAPI.getV1EventsNtaCall(id, since, until, sinceId, untilId, sinceUlid, untilUlid,
+                includePreprocessing, groupId, null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<NonTradeActivityEvent>() {}.getType())));
+    }
+
+    /**
+     * See {@link EventsApi#subscribeToAdminActionSSE(OffsetDateTime, OffsetDateTime, String, String)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToAdminAction(OffsetDateTime since, OffsetDateTime until, String sinceId,
+            String untilId, SSEListener<AccountStatusEvent> sseListener) throws ApiException {
+        final Request request = eventsAPI.subscribeToAdminActionSSECall(since, until, sinceId, untilId, null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<SubscribeToAdminActionSSE200ResponseInner>() {}.getType())));
+    }
+
+    /**
+     * See
+     * {@link EventsApi#subscribeToJournalStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String,
+     * String)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToJournalStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId,
+            Integer untilId, String sinceUlid, String untilUlid, String id,
+            SSEListener<AccountStatusEvent> sseListener) throws ApiException {
+        final Request request = eventsAPI.subscribeToJournalStatusSSECall(since, until, sinceId, untilId, sinceUlid,
+                untilUlid, id, null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<JournalStatusEvent>() {}.getType())));
+    }
+
+    /**
+     * See {@link EventsApi#subscribeToTradeSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToTrade(OffsetDateTime since, OffsetDateTime until, Integer sinceId,
+            Integer untilId, String sinceUlid, String untilUlid, SSEListener<AccountStatusEvent> sseListener)
+            throws ApiException {
+        final Request request = eventsAPI.subscribeToTradeSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid,
+                null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<TradeUpdateEvent>() {}.getType())));
+    }
+
+    /**
+     * See {@link EventsApi#subscribeToTradeV2SSE(OffsetDateTime, OffsetDateTime, String, String)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToTradeV2(OffsetDateTime since, OffsetDateTime until, String sinceId,
+            String untilId, SSEListener<AccountStatusEvent> sseListener) throws ApiException {
+        final Request request = eventsAPI.subscribeToTradeV2SSECall(since, until, sinceId, untilId, null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<TradeUpdateEventV2>() {}.getType())));
+    }
+
+    /**
+     * See
+     * {@link EventsApi#subscribeToTransferStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String,
+     * String)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToTransferStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId,
+            Integer untilId, String sinceUlid, String untilUlid, SSEListener<AccountStatusEvent> sseListener)
+            throws ApiException {
+        final Request request = eventsAPI.subscribeToTransferStatusSSECall(since, until, sinceId, untilId, sinceUlid,
+                untilUlid, null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<TransferStatusEvent>() {}.getType())));
+    }
+
+    /**
+     * See
+     * {@link EventsApi#suscribeToAccountStatusSSE(LocalDate, LocalDate, Integer, Integer, String, String, String)}.
+     *
+     * @return a {@link SSERequest}
+     */
+    public SSERequest subscribeToAccountStatus(LocalDate since, LocalDate until, Integer sinceId, Integer untilId,
+            String sinceUlid, String untilUlid, String id, SSEListener<AccountStatusEvent> sseListener)
+            throws ApiException {
+        final Request request = eventsAPI.suscribeToAccountStatusSSECall(since, until, sinceId, untilId, sinceUlid,
+                untilUlid, id, null).request();
+        return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener,
+                new TypeToken<AccountStatusEvent>() {}.getType())));
+    }
+
+    private <T> EventSourceListener createEventSourceListener(SSEListener<T> sseListener, Type responseTypeToken) {
+        return new EventSourceListener() {
+            @Override
+            public void onClosed(@NotNull EventSource eventSource) {
+                LOGGER.info("Event source closed: eventSource={}", eventSource);
+                sseListener.onClose();
+            }
+
+            @Override
+            public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type,
+                    @NotNull String data) {
+                LOGGER.trace("Event source event: eventSource={} id={}, type={}, data={}", eventSource, id, type, data);
+                sseListener.onMessage(getGson().fromJson(data, responseTypeToken));
+            }
+
+            @Override
+            public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable throwable,
+                    @Nullable Response response) {
+                if (throwable != null && throwable.getMessage().equals("canceled")) {
+                    sseListener.onClose();
+                    return;
+                }
+                LOGGER.error("Event source failure: eventSource={} throwable={}, response={}",
+                        eventSource, throwable, response);
+                sseListener.onError(throwable, response);
+            }
+
+            @Override
+            public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
+                LOGGER.info("Event source opened: {}", eventSource);
+                sseListener.onOpen();
+            }
+        };
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java
deleted file mode 100644
index d1e5b88b..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType;
-import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static com.google.common.base.Preconditions.checkState;
-import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON;
-
-/**
- * {@link AlpacaEndpoint} for
- * <a href="https://docs.alpaca.markets/api-documentation/api-v2/account-activities/">Account
- * Activities</a>.
- */
-public class AccountActivitiesEndpoint extends AlpacaEndpoint {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(AccountActivitiesEndpoint.class);
-
-    private static final String ACTIVITY_TYPE_FIELD = "activity_type";
-
-    /**
-     * Instantiates a new {@link AccountActivitiesEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public AccountActivitiesEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "account/activities");
-    }
-
-    /**
-     * Returns {@link AccountActivity} entries for many {@link ActivityType}s or for a specific {@link ActivityType}.
-     *
-     * @param date          the date for which you want to see activities.
-     * @param until         the response will contain only activities submitted before this date. (Cannot be used with
-     *                      <code>date</code>.)
-     * @param after         the response will contain only activities submitted after this date. (Cannot be used with
-     *                      <code>date</code>.)
-     * @param sortDirection the {@link SortDirection} (defaults to {@link SortDirection#DESCENDING} if unspecified.)
-     * @param pageSize      the maximum number of entries to return in the response. (See the section on paging.)
-     * @param pageToken     the ID of the end of your current page of results. (See the section on paging.)
-     * @param activityTypes the {@link ActivityType}s (null for all {@link ActivityType}s)
-     *
-     * @return a {@link List} of {@link AccountActivity}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<AccountActivity> get(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after,
-            SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes)
-            throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegments(endpointPathSegment);
-
-        if (activityTypes != null) { // Check if we don't want all activity types
-            if (activityTypes.length == 1) { // Get one activity
-                urlBuilder.addPathSegment(activityTypes[0].toString());
-            } else { // Get list of activities
-                urlBuilder.addQueryParameter("activity_types",
-                        // Makes comma-separated list
-                        Arrays.stream(activityTypes).map(ActivityType::toString).collect(Collectors.joining(",")));
-            }
-        }
-
-        if (date != null) {
-            urlBuilder.addQueryParameter("date", date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
-        }
-
-        if (until != null) {
-            urlBuilder.addQueryParameter("until", until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
-        }
-
-        if (after != null) {
-            urlBuilder.addQueryParameter("after", after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
-        }
-
-        if (sortDirection != null) {
-            urlBuilder.addQueryParameter("direction", sortDirection.toString());
-        }
-
-        if (pageSize != null) {
-            urlBuilder.addQueryParameter("page_size", pageSize.toString());
-        }
-
-        if (pageToken != null) {
-            urlBuilder.addQueryParameter("page_token", pageToken);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-
-        JsonElement responseJSON = alpacaClient.requestJSON(request);
-        checkState(responseJSON instanceof JsonArray, "The response must be an array! Received: %s", responseJSON);
-
-        ArrayList<AccountActivity> accountActivities = new ArrayList<>();
-        for (JsonElement responseArrayElement : responseJSON.getAsJsonArray()) {
-            checkState(responseArrayElement instanceof JsonObject,
-                    "All array elements must be objects! Received: %s", responseArrayElement);
-
-            JsonObject responseArrayObject = responseArrayElement.getAsJsonObject();
-            JsonElement activityTypeElement = responseArrayObject.get(ACTIVITY_TYPE_FIELD);
-            checkState(activityTypeElement != null, "Activity type elements must have %s field! Received: %s",
-                    ACTIVITY_TYPE_FIELD, responseArrayElement);
-
-            String activityType = activityTypeElement.getAsString();
-            AccountActivity accountActivity;
-
-            // A 'TradeActivity' always has 'activity_type' field as 'FILL'
-            if (activityType.equals(ActivityType.FILL.toString())) {
-                accountActivity = GSON.fromJson(responseArrayObject, TradeActivity.class);
-            } else {
-                accountActivity = GSON.fromJson(responseArrayObject, NonTradeActivity.class);
-            }
-
-            if (accountActivity.getActivityType() == null) {
-                LOGGER.error("No ActivityType enum exists for {}. Report this!", activityType);
-            }
-
-            accountActivities.add(accountActivity);
-        }
-
-        return accountActivities;
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java
deleted file mode 100644
index 2d7c5de0..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON;
-
-/**
- * {@link AlpacaEndpoint} for
- * <a href="https://alpaca.markets/docs/api-documentation/api-v2/account-configuration/">Account Configuration</a>.
- */
-public class AccountConfigurationEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link AccountConfigurationEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public AccountConfigurationEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "account/configurations");
-    }
-
-    /**
-     * Returns the {@link AccountConfiguration}.
-     *
-     * @return the {@link AccountConfiguration}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public AccountConfiguration get() throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegments(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, AccountConfiguration.class);
-    }
-
-    /**
-     * Sets the {@link AccountConfiguration}.
-     *
-     * @param accountConfiguration the {@link AccountConfiguration}
-     *
-     * @return the updated {@link AccountConfiguration}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public AccountConfiguration set(AccountConfiguration accountConfiguration) throws AlpacaClientException {
-        checkNotNull(accountConfiguration);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegments(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .patch(new JSONBodyBuilder(GSON.toJson(accountConfiguration)).build())
-                .build();
-        return alpacaClient.requestObject(request, AccountConfiguration.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java
deleted file mode 100644
index ff57ff67..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import net.jacobpeterson.alpaca.model.endpoint.account.Account;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://docs.alpaca.markets/api-documentation/api-v2/account/">Account</a>.
- */
-public class AccountEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link AccountEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public AccountEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "account");
-    }
-
-    /**
-     * Returns the {@link Account}.
-     *
-     * @return the {@link Account}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Account get() throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Account.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java
deleted file mode 100644
index dc4e1616..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-
-/**
- * {@link AlpacaEndpoint} is an abstract class representing a RestAPI endpoint.
- */
-public abstract class AlpacaEndpoint {
-
-    protected final AlpacaClient alpacaClient;
-    protected final String endpointPathSegment;
-
-    /**
-     * Instantiates a new {@link AlpacaEndpoint}.
-     *
-     * @param alpacaClient        the {@link AlpacaClient}
-     * @param endpointPathSegment the endpoint path segment relative to {@link AlpacaClient#getBaseURL()}
-     */
-    public AlpacaEndpoint(AlpacaClient alpacaClient, String endpointPathSegment) {
-        this.alpacaClient = alpacaClient;
-        this.endpointPathSegment = endpointPathSegment;
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java
deleted file mode 100644
index c0642115..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.reflect.TypeToken;
-import net.jacobpeterson.alpaca.model.endpoint.asset.Asset;
-import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://docs.alpaca.markets/api-documentation/api-v2/assets/">Assets</a>.
- */
-public class AssetsEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link AssetsEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public AssetsEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "assets");
-    }
-
-    /**
-     * Get a list of {@link Asset}s.
-     *
-     * @param assetStatus the {@link AssetStatus}. By default, all {@link AssetStatus}es are included.
-     * @param assetClass  the asset class. Defaults to "us_equity".
-     *
-     * @return a {@link List} of {@link Asset}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<Asset> get(AssetStatus assetStatus, String assetClass) throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-
-        if (assetStatus != null) {
-            urlBuilder.addQueryParameter("status", assetStatus.toString());
-        }
-
-        if (assetClass != null) {
-            urlBuilder.addQueryParameter("asset_class", assetClass);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, new TypeToken<ArrayList<Asset>>() {}.getType());
-    }
-
-    /**
-     * Gets an {@link Asset} by a symbol or {@link Asset#getId()}.
-     *
-     * @param symbolOrAssetID the symbol or {@link Asset#getId()}
-     *
-     * @return the {@link Asset}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Asset getBySymbol(String symbolOrAssetID) throws AlpacaClientException {
-        checkNotNull(symbolOrAssetID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbolOrAssetID);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Asset.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java
deleted file mode 100644
index 03bb2641..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.reflect.TypeToken;
-import net.jacobpeterson.alpaca.model.endpoint.calendar.Calendar;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://docs.alpaca.markets/api-documentation/api-v2/calendar/">Calendar</a>.
- */
-public class CalendarEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link CalendarEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public CalendarEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "calendar");
-    }
-
-    /**
-     * Returns the market {@link Calendar} from 1970 to 2029.
-     *
-     * @return a {@link List} of {@link Calendar}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<Calendar> get() throws AlpacaClientException {
-        return get(null, null);
-    }
-
-    /**
-     * Returns the market {@link Calendar}.
-     *
-     * @param start the first date to retrieve data for (inclusive)
-     * @param end   the last date to retrieve data for (inclusive)
-     *
-     * @return a {@link List} of {@link Calendar}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<Calendar> get(LocalDate start, LocalDate end) throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-
-        if (start != null) {
-            urlBuilder.addQueryParameter("start", start.format(DateTimeFormatter.ISO_DATE));
-        }
-
-        if (end != null) {
-            urlBuilder.addQueryParameter("end", end.format(DateTimeFormatter.ISO_DATE));
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, new TypeToken<ArrayList<Calendar>>() {}.getType());
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java
deleted file mode 100644
index 6e5e9ae4..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import net.jacobpeterson.alpaca.model.endpoint.clock.Clock;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://docs.alpaca.markets/api-documentation/api-v2/clock/">Clock</a>.
- */
-public class ClockEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link ClockEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public ClockEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "clock");
-    }
-
-    /**
-     * Returns the market {@link Clock}.
-     *
-     * @return the market {@link Clock}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Clock get() throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Clock.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java
deleted file mode 100644
index 9a270e21..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java
+++ /dev/null
@@ -1,276 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.reflect.TypeToken;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.BarsResponse;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarsTimeFrame;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.LatestQuoteResponse;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.QuotesResponse;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.snapshot.Snapshot;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.LatestTradeResponse;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.TradesResponse;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import net.jacobpeterson.alpaca.util.format.FormatUtil;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.time.ZonedDateTime;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * {@link AlpacaEndpoint} for
- * <a href="https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/">Historical
- * Market Data API v2</a>.
- */
-public class MarketDataEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link MarketDataEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public MarketDataEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "stocks");
-    }
-
-    /**
-     * Gets {@link Trade} historical data for the requested security.
-     *
-     * @param symbol    the symbol to query for
-     * @param start     filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not
-     *                  accepted.
-     * @param end       filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not
-     *                  accepted.
-     * @param limit     number of data points to return. Must be in range 1-10000, defaults to 1000 if <code>null</code>
-     *                  is given
-     * @param pageToken pagination token to continue from
-     *
-     * @return the {@link TradesResponse}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit,
-            String pageToken) throws AlpacaClientException {
-        checkNotNull(symbol);
-        checkNotNull(start);
-        checkNotNull(end);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbol)
-                .addPathSegment("trades");
-
-        urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start));
-        urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end));
-
-        if (limit != null) {
-            urlBuilder.addQueryParameter("limit", limit.toString());
-        }
-
-        if (pageToken != null) {
-            urlBuilder.addQueryParameter("page_token", pageToken);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, TradesResponse.class);
-    }
-
-    /**
-     * The Latest Trade API provides the latest {@link Trade} data for a given ticker symbol.
-     *
-     * @param symbol the symbol to query for
-     *
-     * @return the {@link LatestTradeResponse}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaClientException {
-        checkNotNull(symbol);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbol)
-                .addPathSegment("trades")
-                .addPathSegment("latest");
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, LatestTradeResponse.class);
-    }
-
-    /**
-     * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security.
-     *
-     * @param symbol    the symbol to query for
-     * @param start     filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not
-     *                  accepted.
-     * @param end       filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not
-     *                  accepted.
-     * @param limit     number of data points to return. Must be in range 1-10000, defaults to 1000 if <code>null</code>
-     *                  is given
-     * @param pageToken pagination token to continue from
-     *
-     * @return the {@link QuotesResponse}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit,
-            String pageToken) throws AlpacaClientException {
-        checkNotNull(symbol);
-        checkNotNull(start);
-        checkNotNull(end);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbol)
-                .addPathSegment("quotes");
-
-        urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start));
-        urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end));
-
-        if (limit != null) {
-            urlBuilder.addQueryParameter("limit", limit.toString());
-        }
-
-        if (pageToken != null) {
-            urlBuilder.addQueryParameter("page_token", pageToken);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, QuotesResponse.class);
-    }
-
-    /**
-     * The Latest Quote API provides the latest quote data for a given ticker symbol.
-     *
-     * @param symbol the symbol to query for
-     *
-     * @return the {@link LatestQuoteResponse}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaClientException {
-        checkNotNull(symbol);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbol)
-                .addPathSegment("quotes")
-                .addPathSegment("latest");
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, LatestQuoteResponse.class);
-    }
-
-    /**
-     * Gets {@link Bar} aggregate historical data for the requested security.
-     *
-     * @param symbol    the symbol to query for
-     * @param start     filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not
-     *                  accepted.
-     * @param end       filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not
-     *                  accepted.
-     * @param limit     number of data points to return. Must be in range 1-10000, defaults to 1000 if <code>null</code>
-     *                  is given
-     * @param pageToken pagination token to continue from
-     * @param timeFrame the {@link BarsTimeFrame} for the aggregation
-     *
-     * @return the {@link BarsResponse}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit,
-            String pageToken, BarsTimeFrame timeFrame) throws AlpacaClientException {
-        checkNotNull(symbol);
-        checkNotNull(start);
-        checkNotNull(end);
-        checkNotNull(timeFrame);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbol)
-                .addPathSegment("bars");
-
-        urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start));
-        urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end));
-        urlBuilder.addQueryParameter("timeframe", timeFrame.toString());
-
-        if (limit != null) {
-            urlBuilder.addQueryParameter("limit", limit.toString());
-        }
-
-        if (pageToken != null) {
-            urlBuilder.addQueryParameter("page_token", pageToken);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, BarsResponse.class);
-    }
-
-    /**
-     * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar, and previous daily
-     * bar data for a given ticker symbol
-     *
-     * @param symbols a {@link Collection} of symbols to query for
-     *
-     * @return a {@link Map} with they keys being the symbol and their values being the {@link Snapshot}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Map<String, Snapshot> getSnapshots(Collection<String> symbols) throws AlpacaClientException {
-        checkNotNull(symbols);
-        checkArgument(!symbols.isEmpty(), "'symbols' cannot be empty!");
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment("snapshots");
-
-        urlBuilder.addQueryParameter("symbols", String.join(",", symbols));
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, new TypeToken<HashMap<String, Snapshot>>() {}.getType());
-    }
-
-    /**
-     * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily
-     * bar data for a given ticker symbol
-     *
-     * @param symbol the symbol to query for
-     *
-     * @return the {@link Snapshot}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Snapshot getSnapshot(String symbol) throws AlpacaClientException {
-        checkNotNull(symbol);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbol)
-                .addPathSegment("shapshot");
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Snapshot.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java
deleted file mode 100644
index a542d12d..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java
+++ /dev/null
@@ -1,639 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.reflect.TypeToken;
-import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection;
-import net.jacobpeterson.alpaca.model.endpoint.order.CancelledOrder;
-import net.jacobpeterson.alpaca.model.endpoint.order.Order;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import net.jacobpeterson.alpaca.util.format.FormatUtil;
-import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://alpaca.markets/docs/api-documentation/api-v2/orders/">Orders</a>.
- */
-public class OrdersEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link OrdersEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public OrdersEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "orders");
-    }
-
-    /**
-     * Retrieves a list of {@link Order}s for the account, filtered by the supplied query parameters.
-     *
-     * @param status    {@link CurrentOrderStatus} to be queried. Defaults to {@link CurrentOrderStatus#OPEN}.
-     * @param limit     the maximum number of orders in response. Defaults to 50 and max is 500.
-     * @param after     the response will include only ones submitted after this timestamp (exclusive)
-     * @param until     the response will include only ones submitted until this timestamp (exclusive)
-     * @param direction the chronological order of response based on the submission time. Defaults to {@link
-     *                  SortDirection#DESCENDING}.
-     * @param nested    if true, the result will roll up multi-leg orders under the legs field of primary order.
-     * @param symbols   a {@link Collection} of symbols to filter the result by (null for no filter).
-     *
-     * @return a {@link List} of {@link Order}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<Order> get(CurrentOrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until,
-            SortDirection direction, Boolean nested, Collection<String> symbols) throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-
-        if (status != null) {
-            urlBuilder.addQueryParameter("status", status.toString());
-        }
-
-        if (limit != null) {
-            urlBuilder.addQueryParameter("limit", limit.toString());
-        }
-
-        if (after != null) {
-            urlBuilder.addQueryParameter("after", after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
-        }
-
-        if (until != null) {
-            urlBuilder.addQueryParameter("until", until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
-        }
-
-        if (direction != null) {
-            urlBuilder.addQueryParameter("direction", direction.toString());
-        }
-
-        if (nested != null) {
-            urlBuilder.addQueryParameter("nested", nested.toString());
-        }
-
-        if (symbols != null && !symbols.isEmpty()) {
-            urlBuilder.addQueryParameter("symbols", String.join(",", symbols));
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, new TypeToken<ArrayList<Order>>() {}.getType());
-    }
-
-    /**
-     * Places a new order for the given account. An order request may be rejected if the account is not authorized for
-     * trading, or if the tradable balance is insufficient to fill the order. Note: many of the parameters for this
-     * method can be set to <code>null</code> if they aren't required for the requested {@link OrderType}.
-     *
-     * @param symbol               symbol or asset ID to identify the asset to trade
-     * @param quantity             number of shares to trade. Can be fractional.
-     * @param notional             dollar amount to trade. Cannot work with <code>quantity</code>. Can only work for
-     *                             {@link OrderType#MARKET} and {@link OrderTimeInForce#DAY}.
-     * @param side                 the {@link OrderSide}
-     * @param type                 the {@link OrderType}
-     * @param timeInForce          the {@link OrderTimeInForce}
-     * @param limitPrice           required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT}
-     * @param stopPrice            required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT}
-     * @param trailPrice           this or <code>trail_percent</code> is required if type is {@link
-     *                             OrderType#TRAILING_STOP}
-     * @param trailPercent         this or <code>trail_price</code> is required if type is {@link
-     *                             OrderType#TRAILING_STOP}
-     * @param extendedHours        (default) false. If true, order will be eligible to execute in premarket/afterhours.
-     *                             Only works with type {@link OrderType#LIMIT} and {@link OrderTimeInForce#DAY}.
-     * @param clientOrderId        a unique identifier for the order. Automatically generated if <code>null</code>.
-     * @param orderClass           the {@link OrderClass}. For details of non-simple order classes, please see "Bracket
-     *                             Order Overview" on the Alpaca Docs.
-     * @param takeProfitLimitPrice additional parameter for take-profit leg of advanced orders. Required for {@link
-     *                             OrderClass#BRACKET}.
-     * @param stopLossStopPrice    additional parameters for stop-loss leg of advanced orders. Required for {@link
-     *                             OrderClass#BRACKET}.
-     * @param stopLossLimitPrice   additional parameters for stop-loss leg of advanced orders. The stop-loss order
-     *                             becomes a stop-limit order if specified.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestOrder(String symbol, Double quantity, Double notional, OrderSide side, OrderType type,
-            OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent,
-            Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice,
-            Double stopLossStopPrice, Double stopLossLimitPrice)
-            throws AlpacaClientException {
-        checkNotNull(symbol);
-        checkState(quantity != null ^ notional != null, "Either 'quantity' or 'notional' are required.");
-        checkNotNull(side);
-        checkNotNull(type);
-        checkNotNull(timeInForce);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-
-        JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder();
-        jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol);
-        jsonBodyBuilder.appendJSONBodyProperty("side", side.toString());
-        jsonBodyBuilder.appendJSONBodyProperty("type", type.toString());
-        jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString());
-
-        if (quantity != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("qty", quantity.toString());
-        } else {
-            jsonBodyBuilder.appendJSONBodyProperty("notional", notional.toString());
-        }
-
-        if (limitPrice != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("limit_price", FormatUtil.toCurrencyFormat(limitPrice));
-        }
-
-        if (stopPrice != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("stop_price", FormatUtil.toCurrencyFormat(stopPrice));
-        }
-
-        if (trailPrice != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("trail_price", FormatUtil.toCurrencyFormat(trailPrice));
-        }
-
-        if (trailPercent != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("trail_percent", FormatUtil.toCurrencyFormat(trailPercent));
-        }
-
-        if (extendedHours != null) {
-            jsonBodyBuilder.appendJSONBodyJSONProperty("extended_hours", new JsonPrimitive(extendedHours));
-        }
-
-        if (clientOrderId != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("client_order_id", clientOrderId);
-        }
-
-        if (orderClass != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("order_class", orderClass.toString());
-        }
-
-        if (takeProfitLimitPrice != null) {
-            JsonObject takeProfitObject = new JsonObject();
-            takeProfitObject.addProperty("limit_price", takeProfitLimitPrice);
-            jsonBodyBuilder.appendJSONBodyJSONProperty("take_profit", takeProfitObject);
-        }
-
-        if (stopLossStopPrice != null || stopLossLimitPrice != null) {
-            JsonObject stopLossObject = new JsonObject();
-
-            if (stopLossStopPrice != null) {
-                stopLossObject.addProperty("stop_price", stopLossStopPrice);
-            }
-
-            if (stopLossLimitPrice != null) {
-                stopLossObject.addProperty("limit_price", stopLossLimitPrice);
-            }
-
-            jsonBodyBuilder.appendJSONBodyJSONProperty("stop_loss", stopLossObject);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .post(jsonBodyBuilder.build())
-                .build();
-        return alpacaClient.requestObject(request, Order.class);
-    }
-
-    /**
-     * A market order is a request to buy or sell a security at the currently available market price. It provides the
-     * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link
-     * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double,
-     * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} so check the Javadoc for that
-     * method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce)
-            throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null,
-                null, null, null, null, OrderClass.SIMPLE, null, null, null);
-    }
-
-    /**
-     * A market order is a request to buy or sell a security at the currently available market price. It provides the
-     * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link
-     * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double,
-     * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link
-     * OrderTimeInForce#DAY} with a fractional quantity so check the Javadoc for that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderSide side)
-            throws AlpacaClientException {
-        return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null,
-                null, null, null, null, null, null, null);
-    }
-
-    /**
-     * A market order is a request to buy or sell a security at the currently available market price. It provides the
-     * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link
-     * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double,
-     * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link
-     * OrderTimeInForce#DAY} with a notional dollar amount so check the Javadoc for that method for the parameter
-     * details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSide side)
-            throws AlpacaClientException {
-        return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null,
-                null, null, null, null, null, null, null);
-    }
-
-    /**
-     * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy)
-     * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order
-     * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify
-     * the limit price parameter when submitting your order. This method calls {@link #requestOrder(String, Double,
-     * Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass,
-     * Double, Double, Double)} with {@link OrderType#LIMIT} so check the Javadoc for that method for the parameter
-     * details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce,
-            Double limitPrice, Boolean extendedHours) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice,
-                null, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null);
-    }
-
-    /**
-     * A stop (market) order is an order to buy or sell a security when its price moves past a particular point,
-     * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the
-     * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit
-     * orders with a limit price that is 4% higher than a stop price &lt; $50 (or 2.5% higher than a stop price &gt;=
-     * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link #requestOrder(String,
-     * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String,
-     * OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc for that method for
-     * parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce,
-            Double stopPrice, Boolean extendedHours) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice,
-                null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null);
-    }
-
-    /**
-     * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with
-     * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit
-     * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order
-     * becomes a limit order to buy or sell at the limit price or better. This method calls {@link #requestOrder(String,
-     * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String,
-     * OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} so check the Javadoc for that method for
-     * parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce,
-            Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce,
-                limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null);
-    }
-
-    /**
-     * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a
-     * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link
-     * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double,
-     * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a
-     * bracket order so check the Javadoc for that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestMarketBracketOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice,
-            Double stopLossLimitPrice) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null,
-                null, null, null, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice,
-                stopLossLimitPrice);
-    }
-
-    /**
-     * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a
-     * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link
-     * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double,
-     * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a
-     * bracket order so check the Javadoc for that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestLimitBracketOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice,
-            Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice,
-                null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice,
-                stopLossLimitPrice);
-    }
-
-    /**
-     * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side
-     * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the
-     * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one
-     * order submission. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for the parameter
-     * details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestOCOOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice,
-            Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, null, null,
-                null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice,
-                stopLossLimitPrice);
-    }
-
-    /**
-     * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in
-     * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for
-     * that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestOTOMarketOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice,
-            Double stopLossLimitPrice) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null,
-                null, null, null, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice,
-                stopLossLimitPrice);
-    }
-
-    /**
-     * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in
-     * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for
-     * that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestOTOLimitOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice,
-            Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice,
-                null, null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice,
-                stopLossStopPrice, stopLossLimitPrice);
-    }
-
-    /**
-     * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in
-     * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * {@link OrderType#STOP} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for
-     * that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestOTOStopOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice,
-            Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice,
-                null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice,
-                stopLossLimitPrice);
-    }
-
-    /**
-     * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in
-     * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * {@link OrderType#STOP_LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc
-     * for that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours,
-            Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice)
-            throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce,
-                limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER,
-                takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice);
-    }
-
-    /**
-     * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on
-     * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc
-     * for that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null,
-                null, trailPrice, null, extendedHours, null, null, null, null, null);
-    }
-
-    /**
-     * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on
-     * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType,
-     * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with
-     * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc
-     * for that method for parameter details.
-     *
-     * @return the requested {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order requestTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side,
-            OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaClientException {
-        return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null,
-                null, null, trailPercent, extendedHours, null, null, null, null, null);
-    }
-
-    /**
-     * Retrieves a single {@link Order} for the given <code>orderID</code>.
-     *
-     * @param orderID the {@link Order#getId()}
-     * @param nested  if true, the result will roll up multi-leg orders under the legs field of primary order.
-     *
-     * @return the {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order get(String orderID, Boolean nested) throws AlpacaClientException {
-        checkNotNull(orderID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(orderID);
-
-        if (nested != null) {
-            urlBuilder.addQueryParameter("nested", nested.toString());
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Order.class);
-    }
-
-    /**
-     * Retrieves a single {@link Order} for the given <code>clientOrderId</code>.
-     *
-     * @param clientOrderID the {@link Order#getClientOrderId()}
-     *
-     * @return the {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order getByClientID(String clientOrderID) throws AlpacaClientException {
-        checkNotNull(clientOrderID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment("orders:by_client_order_id");
-        urlBuilder.addQueryParameter("client_order_id", clientOrderID);
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Order.class);
-    }
-
-    /**
-     * Replaces a single {@link Order} with updated parameters. Each parameter overrides the corresponding attribute of
-     * the existing {@link Order}. The other attributes remain the same as the existing {@link Order}.
-     *
-     * @param orderID       the {@link Order#getId()}
-     * @param quantity      number of shares to trade. Can be fractional.
-     * @param timeInForce   the {@link OrderTimeInForce}
-     * @param limitPrice    required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT}
-     * @param stopPrice     required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT}
-     * @param trail         the new value of the <code>trail_price</code> or <code>trail_percent</code> value (works
-     *                      only for {@link Order#getType()} <code>==</code> {@link OrderType#TRAILING_STOP})
-     * @param clientOrderId a unique identifier for the order. Automatically generated if <code>null</code>.
-     *
-     * @return a new {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order replace(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice,
-            Double stopPrice, Double trail, String clientOrderId) throws AlpacaClientException {
-        checkNotNull(orderID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(orderID);
-
-        JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder();
-
-        if (quantity != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("qty", quantity.toString());
-        }
-
-        if (timeInForce != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString());
-        }
-
-        if (limitPrice != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("limit_price", FormatUtil.toCurrencyFormat(limitPrice));
-        }
-
-        if (stopPrice != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("stop_price", FormatUtil.toCurrencyFormat(stopPrice));
-        }
-
-        if (trail != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("trail", FormatUtil.toCurrencyFormat(trail));
-        }
-
-        if (clientOrderId != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("client_order_id", clientOrderId);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .patch(jsonBodyBuilder.build())
-                .build();
-        return alpacaClient.requestObject(request, Order.class);
-    }
-
-    /**
-     * Attempts to cancel all open {@link Order}s. A response will be provided for each {@link Order} that is attempted
-     * to be cancelled. If an {@link Order} is no longer cancelable, the server will respond with status
-     * <code>500</code> and reject the request.
-     *
-     * @return a {@link List} of {@link CancelledOrder}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<CancelledOrder> cancelAll() throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .delete()
-                .build();
-        return alpacaClient.requestObject(request, (code) -> code == 200 || code == 207,
-                new TypeToken<ArrayList<CancelledOrder>>() {}.getType());
-    }
-
-    /**
-     * Attempts to cancel an open {@link Order}. If the {@link Order} is no longer cancelable (example: {@link
-     * Order#getStatus()} <code>==</code> {@link OrderStatus#FILLED}), the server will respond with status
-     * <code>422</code>, and reject the request.
-     *
-     * @param orderID the {@link Order#getId()}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public void cancel(String orderID) throws AlpacaClientException {
-        checkNotNull(orderID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(orderID);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .delete()
-                .build();
-        alpacaClient.requestVoid(request, (code) -> code == 200 || code == 204);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java
deleted file mode 100644
index 9bab136d..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistory;
-import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint;
-import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryResponse;
-import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioPeriodUnit;
-import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import net.jacobpeterson.alpaca.util.format.FormatUtil;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-
-import static com.google.common.base.Preconditions.checkState;
-
-/**
- * {@link AlpacaEndpoint} for
- * <a href="https://alpaca.markets/docs/api-documentation/api-v2/portfolio-history/">Portfolio
- * History</a>.
- */
-public class PortfolioHistoryEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link PortfolioHistoryEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public PortfolioHistoryEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "account/portfolio/history");
-    }
-
-    /**
-     * Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan.
-     *
-     * @param periodLength  the duration of the <code>periodUnit</code>. Defaults to <code>1</code>.
-     * @param periodUnit    the {@link PortfolioPeriodUnit}. Defaults to {@link PortfolioPeriodUnit#MONTH}.
-     * @param timeFrame     the resolution of time window. If omitted, {@link PortfolioTimeFrame#ONE_MIN} for less than
-     *                      7 days period, {@link PortfolioTimeFrame#FIFTEEN_MINUTE} for less than 30 days, or otherwise
-     *                      {@link PortfolioTimeFrame#ONE_DAY}.
-     * @param dateEnd       the date the data is returned up to. Defaults to the current market date (rolls over at the
-     *                      market open if <code>extended_hours</code> is false, otherwise at 7 AM ET)
-     * @param extendedHours if true, include extended hours in the result. This is effective only for timeframe less
-     *                      than {@link PortfolioTimeFrame#ONE_DAY}.
-     *
-     * @return the {@link PortfolioHistory}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit, PortfolioTimeFrame timeFrame,
-            LocalDate dateEnd, Boolean extendedHours) throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegments(endpointPathSegment);
-
-        if (periodLength != null && periodUnit != null) {
-            urlBuilder.addQueryParameter("period", periodLength + periodUnit.toString());
-        }
-
-        if (timeFrame != null) {
-            urlBuilder.addQueryParameter("timeframe", timeFrame.toString());
-        }
-
-        if (dateEnd != null) {
-            urlBuilder.addQueryParameter("date_end", dateEnd.format(DateTimeFormatter.ISO_DATE));
-        }
-
-        if (extendedHours != null) {
-            urlBuilder.addQueryParameter("extended_hours", extendedHours.toString());
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        PortfolioHistoryResponse response = alpacaClient.requestObject(request,
-                PortfolioHistoryResponse.class);
-
-        // Check if any response arrays differ in size
-        checkState(response.getTimestamp().size() == response.getEquity().size() &&
-                        response.getEquity().size() == response.getProfitLoss().size() &&
-                        response.getProfitLoss().size() == response.getProfitLossPct().size(),
-                "Response arrays should not differ in size!");
-
-        // Add all data points into one POJO
-        ArrayList<PortfolioHistoryDataPoint> dataPoints = new ArrayList<>();
-        for (int index = 0; index < response.getTimestamp().size(); index++) {
-            dataPoints.add(new PortfolioHistoryDataPoint(
-                    Instant.ofEpochSecond(response.getTimestamp().get(index)).atZone(FormatUtil.NEW_YORK_ZONED_ID),
-                    response.getEquity().get(index),
-                    response.getProfitLoss().get(index),
-                    response.getProfitLossPct().get(index)));
-        }
-
-        return new PortfolioHistory(
-                dataPoints,
-                response.getBaseValue(),
-                response.getTimeframe());
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java
deleted file mode 100644
index 676537ba..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.reflect.TypeToken;
-import net.jacobpeterson.alpaca.model.endpoint.asset.Asset;
-import net.jacobpeterson.alpaca.model.endpoint.order.Order;
-import net.jacobpeterson.alpaca.model.endpoint.position.ClosePositionOrder;
-import net.jacobpeterson.alpaca.model.endpoint.position.Position;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://alpaca.markets/docs/api-documentation/api-v2/positions/">Positions</a>.
- */
-public class PositionsEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link PositionsEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public PositionsEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "positions");
-    }
-
-    /**
-     * Retrieves a list of the account's open {@link Position}s.
-     *
-     * @return a {@link List} of open {@link Position}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<Position> get() throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, new TypeToken<ArrayList<Position>>() {}.getType());
-    }
-
-    /**
-     * Retrieves the account's open {@link Position}s for the given symbol or {@link Asset#getId()}.
-     *
-     * @param symbolOrAssetID the symbol or {@link Asset#getId()} (required)
-     *
-     * @return the open {@link Position}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Position getBySymbol(String symbolOrAssetID) throws AlpacaClientException {
-        checkNotNull(symbolOrAssetID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbolOrAssetID);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Position.class);
-    }
-
-    /**
-     * Closes (liquidates) all of the account’s open long and short {@link Position}s. A response will be provided for
-     * each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with
-     * status 500 and reject the request.
-     *
-     * @param cancelOrders if true is specified, cancel all open orders before liquidating all positions
-     *
-     * @return a {@link List} of open {@link ClosePositionOrder}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<ClosePositionOrder> closeAll(Boolean cancelOrders) throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-
-        if (cancelOrders != null) {
-            urlBuilder.addQueryParameter("cancel_orders", cancelOrders.toString());
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .delete()
-                .build();
-        return alpacaClient.requestObject(request, (code) -> code == 200 || code == 207,
-                new TypeToken<ArrayList<ClosePositionOrder>>() {}.getType());
-    }
-
-    /**
-     * Closes (liquidates) the account’s open position for the given <code>symbol</code>. Works for both long and short
-     * positions.
-     *
-     * @param symbolOrAssetID the symbol or {@link Asset#getId()}
-     *
-     * @return a closing {@link Position} {@link Order}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Order close(String symbolOrAssetID, Integer quantity, Double percentage) throws AlpacaClientException {
-        checkNotNull(symbolOrAssetID);
-        checkArgument(quantity != null ^ percentage != null, "Either 'quantity' or 'percentage' are required.");
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(symbolOrAssetID);
-
-        if (quantity != null) {
-            urlBuilder.addQueryParameter("qty", quantity.toString());
-        }
-
-        if (percentage != null) {
-            urlBuilder.addQueryParameter("percentage", percentage.toString());
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .delete()
-                .build();
-        return alpacaClient.requestObject(request, Order.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java
deleted file mode 100644
index aeadb183..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java
+++ /dev/null
@@ -1,211 +0,0 @@
-package net.jacobpeterson.alpaca.rest.endpoint;
-
-import com.google.gson.JsonArray;
-import com.google.gson.reflect.TypeToken;
-import net.jacobpeterson.alpaca.model.endpoint.asset.Asset;
-import net.jacobpeterson.alpaca.model.endpoint.watchlist.Watchlist;
-import net.jacobpeterson.alpaca.rest.AlpacaClient;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * {@link AlpacaEndpoint} for <a href="https://docs.alpaca.markets/api-documentation/api-v2/watchlist/">Watchlists</a>
- * .
- */
-public class WatchlistEndpoint extends AlpacaEndpoint {
-
-    /**
-     * Instantiates a new {@link WatchlistEndpoint}.
-     *
-     * @param alpacaClient the {@link AlpacaClient}
-     */
-    public WatchlistEndpoint(AlpacaClient alpacaClient) {
-        super(alpacaClient, "watchlists");
-    }
-
-    /**
-     * Returns the list of {@link Watchlist}s registered under the account.
-     *
-     * @return a {@link List} of {@link Watchlist}s
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public List<Watchlist> get() throws AlpacaClientException {
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, new TypeToken<ArrayList<Watchlist>>() {}.getType());
-    }
-
-    /**
-     * Creates a new {@link Watchlist} with initial set of {@link Asset}s.
-     *
-     * @param name    arbitrary name string, up to 64 characters
-     * @param symbols set of symbols {@link String}s
-     *
-     * @return the created {@link Watchlist}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Watchlist create(String name, String... symbols) throws AlpacaClientException {
-        checkNotNull(name);
-        checkArgument(name.length() <= 64, "'name' cannot be longer than 64 characters!");
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment);
-
-        JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder();
-        jsonBodyBuilder.appendJSONBodyProperty("name", name);
-
-        if (symbols != null && symbols.length != 0) {
-            JsonArray symbolsArray = new JsonArray();
-            Arrays.stream(symbols).forEach(symbolsArray::add);
-            jsonBodyBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .post(jsonBodyBuilder.build())
-                .build();
-        return alpacaClient.requestObject(request, Watchlist.class);
-    }
-
-    /**
-     * Returns a {@link Watchlist} identified by the ID.
-     *
-     * @param watchlistID the {@link Watchlist#getId()}
-     *
-     * @return the {@link Watchlist}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Watchlist get(String watchlistID) throws AlpacaClientException {
-        checkNotNull(watchlistID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(watchlistID);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .get()
-                .build();
-        return alpacaClient.requestObject(request, Watchlist.class);
-    }
-
-    /**
-     * Updates the name and/or content of a {@link Watchlist}.
-     *
-     * @param watchlistID the {@link Watchlist#getId()}
-     * @param name        the new {@link Watchlist} name
-     * @param symbols     the new list of symbol names to replace the {@link Watchlist} content
-     *
-     * @return the updated {@link Watchlist}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Watchlist update(String watchlistID, String name, String... symbols)
-            throws AlpacaClientException {
-        checkNotNull(watchlistID);
-        checkArgument(name == null || name.length() <= 64, "'name' cannot be longer than 64 characters!");
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(watchlistID);
-
-        JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder();
-
-        if (name != null) {
-            jsonBodyBuilder.appendJSONBodyProperty("name", name);
-        }
-
-        if (symbols != null && symbols.length != 0) {
-            JsonArray symbolsArray = new JsonArray();
-            Arrays.stream(symbols).forEach(symbolsArray::add);
-            jsonBodyBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray);
-        }
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .put(jsonBodyBuilder.build())
-                .build();
-        return alpacaClient.requestObject(request, Watchlist.class);
-    }
-
-    /**
-     * Append an asset for the symbol to the end of {@link Watchlist} asset list.
-     *
-     * @param watchlistID the {@link Watchlist#getId()}
-     * @param symbol      the symbol name to add to the {@link Watchlist}
-     *
-     * @return the updated {@link Watchlist}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Watchlist addAsset(String watchlistID, String symbol) throws AlpacaClientException {
-        checkNotNull(watchlistID);
-        checkNotNull(symbol);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(watchlistID);
-
-        JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder();
-        jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol);
-
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .post(jsonBodyBuilder.build())
-                .build();
-        return alpacaClient.requestObject(request, Watchlist.class);
-    }
-
-    /**
-     * Deletes a {@link Watchlist}. This is a permanent deletion.
-     *
-     * @param watchlistID the {@link Watchlist#getId()}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public void delete(String watchlistID) throws AlpacaClientException {
-        checkNotNull(watchlistID);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(watchlistID);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .delete()
-                .build();
-        alpacaClient.requestVoid(request, (code) -> code == 200 || code == 204);
-    }
-
-    /**
-     * Delete one entry for an asset by symbol name.
-     *
-     * @param watchlistID the {@link Watchlist#getId()}
-     * @param symbol      symbol name to remove from the {@link Watchlist} content
-     *
-     * @return the updated {@link Watchlist}
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    public Watchlist removeSymbol(String watchlistID, String symbol) throws AlpacaClientException {
-        checkNotNull(watchlistID);
-        checkNotNull(symbol);
-
-        HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder()
-                .addPathSegment(endpointPathSegment)
-                .addPathSegment(watchlistID)
-                .addPathSegment(symbol);
-        Request request = alpacaClient.requestBuilder(urlBuilder.build())
-                .delete()
-                .build();
-        return alpacaClient.requestObject(request, (code) -> code == 200 || code == 204, Watchlist.class);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java
new file mode 100644
index 00000000..4206b485
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java
@@ -0,0 +1,152 @@
+package net.jacobpeterson.alpaca.rest.marketdata;
+
+import net.jacobpeterson.alpaca.openapi.marketdata.ApiClient;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.CorporateActionsApi;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.CryptoApi;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.ForexApi;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.LogosApi;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.NewsApi;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.OptionApi;
+import net.jacobpeterson.alpaca.openapi.marketdata.api.StockApi;
+import okhttp3.OkHttpClient;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey;
+
+/**
+ * {@link AlpacaMarketDataAPI} is the class used to interface with the Alpaca Market Data API endpoints. This class is
+ * thread-safe.
+ */
+public class AlpacaMarketDataAPI {
+
+    private final ApiClient apiClient;
+    private CorporateActionsApi corporateActions;
+    private CryptoApi crypto;
+    private ForexApi forex;
+    private LogosApi logos;
+    private NewsApi news;
+    private OptionApi option;
+    private StockApi stock;
+
+    /**
+     * Instantiates a new {@link AlpacaMarketDataAPI}.
+     *
+     * @param traderKeyID     the Trader key ID
+     * @param traderSecretKey the Trader secret key
+     * @param brokerAPIKey    the Broker API key
+     * @param brokerAPISecret the Broker API secret
+     * @param okHttpClient    an existing {@link OkHttpClient} or <code>null</code> to create a new default instance
+     */
+    public AlpacaMarketDataAPI(String traderKeyID, String traderSecretKey,
+            String brokerAPIKey, String brokerAPISecret, OkHttpClient okHttpClient) {
+        checkArgument((traderKeyID != null && traderSecretKey != null) ^
+                        (brokerAPIKey != null && brokerAPISecret != null),
+                "You must specify a (trader key ID and secret key) or an (broker API key and secret)!");
+        checkNotNull(okHttpClient);
+
+        final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null;
+        apiClient = new ApiClient(okHttpClient);
+        apiClient.setServerIndex(traderKeysGiven ? 0 : 1);
+        if (traderKeysGiven) {
+            apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID);
+            apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey);
+        } else {
+            apiClient.addDefaultHeader("Authorization", "Basic " +
+                    createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret));
+        }
+    }
+
+    /**
+     * Gets the internal {@link ApiClient}.
+     *
+     * @return the {@link ApiClient}
+     */
+    public ApiClient getInternalAPIClient() {
+        return apiClient;
+    }
+
+    /**
+     * Gets the {@link CorporateActionsApi}. Lazily instantiated.
+     *
+     * @return the {@link CorporateActionsApi}
+     */
+    public synchronized CorporateActionsApi corporateActions() {
+        if (corporateActions == null) {
+            corporateActions = new CorporateActionsApi(apiClient);
+        }
+        return corporateActions;
+    }
+
+    /**
+     * Gets the {@link CryptoApi}. Lazily instantiated.
+     *
+     * @return the {@link CryptoApi}
+     */
+    public synchronized CryptoApi crypto() {
+        if (crypto == null) {
+            crypto = new CryptoApi(apiClient);
+        }
+        return crypto;
+    }
+
+    /**
+     * Gets the {@link ForexApi}. Lazily instantiated.
+     *
+     * @return the {@link ForexApi}
+     */
+    public synchronized ForexApi forex() {
+        if (forex == null) {
+            forex = new ForexApi(apiClient);
+        }
+        return forex;
+    }
+
+    /**
+     * Gets the {@link LogosApi}. Lazily instantiated.
+     *
+     * @return the {@link LogosApi}
+     */
+    public synchronized LogosApi logos() {
+        if (logos == null) {
+            logos = new LogosApi(apiClient);
+        }
+        return logos;
+    }
+
+    /**
+     * Gets the {@link NewsApi}. Lazily instantiated.
+     *
+     * @return the {@link NewsApi}
+     */
+    public synchronized NewsApi news() {
+        if (news == null) {
+            news = new NewsApi(apiClient);
+        }
+        return news;
+    }
+
+    /**
+     * Gets the {@link OptionApi}. Lazily instantiated.
+     *
+     * @return the {@link OptionApi}
+     */
+    public synchronized OptionApi option() {
+        if (option == null) {
+            option = new OptionApi(apiClient);
+        }
+        return option;
+    }
+
+    /**
+     * Gets the {@link StockApi}. Lazily instantiated.
+     *
+     * @return the {@link StockApi}
+     */
+    public synchronized StockApi stock() {
+        if (stock == null) {
+            stock = new StockApi(apiClient);
+        }
+        return stock;
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java
new file mode 100644
index 00000000..7b560a26
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java
@@ -0,0 +1,221 @@
+package net.jacobpeterson.alpaca.rest.trader;
+
+import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType;
+import net.jacobpeterson.alpaca.openapi.trader.ApiClient;
+import net.jacobpeterson.alpaca.openapi.trader.api.AccountActivitiesApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.AccountConfigurationsApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.AccountsApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.AssetsApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.CalendarApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.ClockApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.CorporateActionsApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.OrdersApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.PortfolioHistoryApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.PositionsApi;
+import net.jacobpeterson.alpaca.openapi.trader.api.WatchlistsApi;
+import net.jacobpeterson.alpaca.openapi.trader.model.NonTradeActivities;
+import net.jacobpeterson.alpaca.openapi.trader.model.TradingActivities;
+import okhttp3.OkHttpClient;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * {@link AlpacaTraderAPI} is the class used to interface with the Alpaca Trader API endpoints. This class is
+ * thread-safe.
+ */
+public class AlpacaTraderAPI {
+
+    // Set validation predicates for 'anyOf' and 'oneOf' models
+    static {
+        // 'AccountConfigurationsApi' models
+        TradingActivities.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type");
+        NonTradeActivities.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type");
+    }
+
+    private final ApiClient apiClient;
+    private AccountActivitiesApi accountActivities;
+    private AccountConfigurationsApi accountConfigurations;
+    private AccountsApi accounts;
+    private AssetsApi assets;
+    private CalendarApi calendar;
+    private ClockApi clock;
+    private CorporateActionsApi traderCorporateActions;
+    private OrdersApi orders;
+    private PortfolioHistoryApi portfolioHistory;
+    private PositionsApi positions;
+    private WatchlistsApi watchlists;
+
+    /**
+     * Instantiates a new {@link AlpacaTraderAPI}.
+     *
+     * @param traderKeyID           the Trader key ID
+     * @param traderSecretKey       the Trader secret key
+     * @param traderOAuthToken      the Trader OAuth token
+     * @param traderAPIEndpointType the {@link TraderAPIEndpointType}
+     * @param okHttpClient          an existing {@link OkHttpClient} or <code>null</code> to create a new default
+     *                              instance
+     */
+    @SuppressWarnings("UnnecessaryDefault")
+    public AlpacaTraderAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken,
+            TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) {
+        checkArgument((traderKeyID != null && traderSecretKey != null) ^ (traderOAuthToken != null),
+                "You must specify a (trader key ID and secret key) or an (OAuth token)!");
+        checkNotNull(traderAPIEndpointType);
+        checkNotNull(okHttpClient);
+
+        apiClient = new ApiClient(okHttpClient);
+        apiClient.setServerIndex(switch (traderAPIEndpointType) {
+            case PAPER -> 0;
+            case LIVE -> 1;
+            default -> throw new UnsupportedOperationException();
+        });
+        if (traderKeyID != null && traderSecretKey != null) {
+            apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID);
+            apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey);
+        } else {
+            apiClient.addDefaultHeader("Authorization", "Bearer " + traderOAuthToken);
+        }
+    }
+
+    /**
+     * Gets the internal {@link ApiClient}.
+     *
+     * @return the {@link ApiClient}
+     */
+    public ApiClient getInternalAPIClient() {
+        return apiClient;
+    }
+
+    /**
+     * Gets the {@link AccountActivitiesApi}. Lazily instantiated.
+     *
+     * @return the {@link AccountActivitiesApi}
+     */
+    public synchronized AccountActivitiesApi accountActivities() {
+        if (accountActivities == null) {
+            accountActivities = new AccountActivitiesApi(apiClient);
+        }
+        return accountActivities;
+    }
+
+    /**
+     * Gets the {@link AccountConfigurationsApi}. Lazily instantiated.
+     *
+     * @return the {@link AccountConfigurationsApi}
+     */
+    public synchronized AccountConfigurationsApi accountConfigurations() {
+        if (accountConfigurations == null) {
+            accountConfigurations = new AccountConfigurationsApi(apiClient);
+        }
+        return accountConfigurations;
+    }
+
+    /**
+     * Gets the {@link AccountsApi}. Lazily instantiated.
+     *
+     * @return {@link AccountsApi}
+     */
+    public synchronized AccountsApi accounts() {
+        if (accounts == null) {
+            accounts = new AccountsApi(apiClient);
+        }
+        return accounts;
+    }
+
+    /**
+     * Gets the {@link AssetsApi}. Lazily instantiated.
+     *
+     * @return {@link AssetsApi}
+     */
+    public synchronized AssetsApi assets() {
+        if (assets == null) {
+            assets = new AssetsApi(apiClient);
+        }
+        return assets;
+    }
+
+    /**
+     * Gets the {@link CalendarApi}. Lazily instantiated.
+     *
+     * @return {@link CalendarApi}
+     */
+    public synchronized CalendarApi calendar() {
+        if (calendar == null) {
+            calendar = new CalendarApi(apiClient);
+        }
+        return calendar;
+    }
+
+    /**
+     * Gets the {@link ClockApi}. Lazily instantiated.
+     *
+     * @return {@link ClockApi}
+     */
+    public synchronized ClockApi clock() {
+        if (clock == null) {
+            clock = new ClockApi(apiClient);
+        }
+        return clock;
+    }
+
+    /**
+     * Gets the {@link CorporateActionsApi}. Lazily instantiated.
+     *
+     * @return the {@link CorporateActionsApi}
+     */
+    public synchronized CorporateActionsApi traderCorporateActions() {
+        if (traderCorporateActions == null) {
+            traderCorporateActions = new CorporateActionsApi(apiClient);
+        }
+        return traderCorporateActions;
+    }
+
+    /**
+     * Gets the {@link OrdersApi}. Lazily instantiated.
+     *
+     * @return {@link OrdersApi}
+     */
+    public synchronized OrdersApi orders() {
+        if (orders == null) {
+            orders = new OrdersApi(apiClient);
+        }
+        return orders;
+    }
+
+    /**
+     * Gets the {@link PortfolioHistoryApi}. Lazily instantiated.
+     *
+     * @return {@link PortfolioHistoryApi}
+     */
+    public synchronized PortfolioHistoryApi portfolioHistory() {
+        if (portfolioHistory == null) {
+            portfolioHistory = new PortfolioHistoryApi(apiClient);
+        }
+        return portfolioHistory;
+    }
+
+    /**
+     * Gets the {@link PositionsApi}. Lazily instantiated.
+     *
+     * @return {@link PositionsApi}
+     */
+    public synchronized PositionsApi positions() {
+        if (positions == null) {
+            positions = new PositionsApi(apiClient);
+        }
+        return positions;
+    }
+
+    /**
+     * Gets the {@link WatchlistsApi}. Lazily instantiated.
+     *
+     * @return {@link WatchlistsApi}
+     */
+    public synchronized WatchlistsApi watchlists() {
+        if (watchlists == null) {
+            watchlists = new WatchlistsApi(apiClient);
+        }
+        return watchlists;
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java
new file mode 100644
index 00000000..c9d0ce07
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java
@@ -0,0 +1,23 @@
+package net.jacobpeterson.alpaca.util.apikey;
+
+import java.util.Base64;
+
+/**
+ * {@link APIKeyUtil} is a utility class for Alpaca API keys.
+ */
+public class APIKeyUtil {
+
+    /**
+     * Creates a broker API auth key.
+     *
+     * @param brokerAPIKey    the broker API key
+     * @param brokerAPISecret the broker API secret
+     *
+     * @return the key {@link String}
+     *
+     * @see <a href="https://docs.alpaca.markets/docs/getting-started-with-broker-api#api-keys">Alpaca Docs</a>
+     */
+    public static String createBrokerAPIAuthKey(String brokerAPIKey, String brokerAPISecret) {
+        return Base64.getEncoder().encodeToString((brokerAPIKey + ":" + brokerAPISecret).getBytes());
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json
new file mode 100644
index 00000000..c4dd1c66
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json
@@ -0,0 +1,8 @@
+{
+  "type": "string",
+  "javaName": "BrokerAPIEndpointType",
+  "enum": [
+    "sandbox",
+    "production"
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json
new file mode 100644
index 00000000..5a27c8fb
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json
@@ -0,0 +1,7 @@
+{
+  "type": "string",
+  "enum": [
+    "iex",
+    "sip"
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json
new file mode 100644
index 00000000..574d4109
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json
@@ -0,0 +1,8 @@
+{
+  "type": "string",
+  "javaName": "TraderAPIEndpointType",
+  "enum": [
+    "paper",
+    "live"
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java
deleted file mode 100644
index 29fbf597..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package net.jacobpeterson.alpaca.util.doc;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpression;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-
-/**
- * The type {@link Documentation2JSONSchema}.
- */
-public class Documentation2JSONSchema {
-
-    /**
-     * This is a standalone helper method that converts properties HTML copied from the Alpaca docs page into a json
-     * schema property list that includes the type and the description. It's kinda hacky, but it helps in the generation
-     * of JSON POJOs with Javadocs. Must be in the format of a list of:
-     * <pre>
-     * &lt;div class="spec-table"&gt;
-     *     &lt;div class="spec-row"&gt;
-     *     &lt;div class="spec-left"&gt;&lt;span class="spec-name"&gt;id&lt;/span&gt;&lt;/div&gt;
-     *     &lt;div class="spec-right"&gt;
-     *         &lt;div class="spec-type"&gt;string&lt;uuid&gt;&lt;/div&gt;
-     *         &lt;div class="spec-desc"&gt;Account ID.&lt;/div&gt;
-     *     &lt;/div&gt;
-     *     &lt;/div&gt;
-     *     ...
-     * &lt;/div&gt;
-     * </pre>
-     *
-     * @param alpacaDocXML the alpaca doc xml
-     */
-    public static void printAlpacaHTMLWebDocAsJSONSchema(String alpacaDocXML)
-            throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
-        String nameXPath = "//span[" + xPathClassName("spec-name") + "]";
-        String typeXPath = "//div[" + xPathClassName("spec-type") + "]";
-        String descriptionXPath = "//div[" + xPathClassName("spec-desc") + "]";
-
-        System.out.println(getSchemaFromDoc(alpacaDocXML, nameXPath, typeXPath, descriptionXPath));
-    }
-
-    /**
-     * Converts a generic HTML doc for Alpaca with JSON property fields for "name", "type", and "description" into
-     * schema JSON used for
-     * <a href="https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference">jsonSchema2POJO</a>.
-     *
-     * @param docHTML          the doc html
-     * @param nameXPath        the name x path
-     * @param typeXPath        the type x path
-     * @param descriptionXPath the description x path
-     *
-     * @return the schema from doc
-     *
-     * @throws ParserConfigurationException the parser configuration exception
-     * @throws SAXException                 the sax exception
-     * @throws IOException                  the io exception
-     * @throws XPathExpressionException     the x path expression exception
-     */
-    private static String getSchemaFromDoc(String docHTML, String nameXPath, String typeXPath, String descriptionXPath)
-            throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
-        String schemaJSON = "";
-        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        Document document = builder.parse(new ByteArrayInputStream(docHTML.getBytes()));
-        XPathFactory xPathFactory = XPathFactory.newInstance();
-        XPath xPath = xPathFactory.newXPath();
-
-        XPathExpression propertyNamesXPath = xPath.compile(nameXPath);
-        XPathExpression propertyTypesXPath = xPath.compile(typeXPath);
-        XPathExpression propertyDescriptionsXPath = xPath.compile(descriptionXPath);
-
-        NodeList propertyNameNodes = (NodeList) propertyNamesXPath.evaluate(document, XPathConstants.NODESET);
-        NodeList propertyTypesNodes = (NodeList) propertyTypesXPath.evaluate(document, XPathConstants.NODESET);
-        NodeList propertyDescriptionsNodes = (NodeList) propertyDescriptionsXPath.evaluate(document,
-                XPathConstants.NODESET);
-
-        for (int propertyIndex = 0; propertyIndex < propertyNameNodes.getLength(); propertyIndex++) {
-            String propertyName = propertyNameNodes.item(propertyIndex).getTextContent();
-            String propertyType = propertyTypesNodes.item(propertyIndex).getTextContent();
-            String propertyDescription = propertyDescriptionsNodes.item(propertyIndex).getTextContent();
-
-            // Property description may be null
-            if (propertyDescription == null || propertyDescription.isEmpty()) {
-                propertyDescription = propertyName;
-            }
-
-            schemaJSON += "\"" + propertyName + "\": {\n";
-            schemaJSON += "\"existingJavaType\": \"" + parseDocTypeToSchemaJavaType(propertyType) + "\",\n";
-            schemaJSON += "\"title\": \"" + propertyDescription + "\"\n";
-            schemaJSON += "},\n";
-        }
-        return schemaJSON;
-    }
-
-    /**
-     * @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference#type">jsonSchema2Pojo Ref</a>
-     */
-    private static String parseDocTypeToSchemaJavaType(String docType) {
-        docType = docType.toLowerCase();
-
-        if (docType.contains("string") || docType.contains("timestamp")) {
-            return "java.lang.String";
-        } else if (docType.contains("boolean") || docType.contains("bool")) {
-            return "java.lang.Boolean";
-        } else if (docType.contains("int") || docType.contains("integer")) {
-            return "java.lang.Integer";
-        } else if (docType.contains("double") || docType.contains("float") ||
-                docType.contains("number")) {
-            return "java.lang.Double";
-        } else {
-            return "UNKNOWN DOC DATA TYPE";
-        }
-    }
-
-    /**
-     * @see <a href="https://devhints.io/xpath#class-check">https://devhints.io/xpath#class-check</a>
-     */
-    private static String xPathClassName(String className) {
-        return String.format("contains(concat(' ',normalize-space(@class),' '),' %s ')", className);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java
deleted file mode 100644
index 0b76f3b5..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package net.jacobpeterson.alpaca.util.format;
-
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.TemporalAccessor;
-import java.util.Locale;
-
-/**
- * {@link FormatUtil} is a utility class for various formatting.
- */
-public class FormatUtil {
-
-    /** The {@link ZoneId} for <code>America/New_York</code>. */
-    public static final ZoneId NEW_YORK_ZONED_ID = ZoneId.of("America/New_York");
-
-    // Alpaca uses the following rounding mechanics with respect to buy orders: (1) rounded down to two decimal
-    // places if the last trade price is over $1.00; otherwise, rounded down to four decimal places, hence the '#' in
-    // the 3rd and 4th least significant decimal digits.
-    private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00##",
-            DecimalFormatSymbols.getInstance(Locale.US));
-    private static final DateTimeFormatter RFC_3339_FORMATTER =
-            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(NEW_YORK_ZONED_ID);
-
-    /**
-     * Formats an arbitrary number to a currency format. e.g. $123.45
-     *
-     * @param numberToFormat the {@link Number} to format
-     *
-     * @return the formatted string
-     */
-    public static String toCurrencyFormat(Number numberToFormat) {
-        return CURRENCY_FORMATTER.format(numberToFormat);
-    }
-
-    /**
-     * Formats a {@link TemporalAccessor} using {@link #RFC_3339_FORMATTER} {@link DateTimeFormatter}.
-     *
-     * @param zonedDateTime the {@link ZonedDateTime}
-     *
-     * @return the formatted string
-     */
-    public static String toRFC3339Format(ZonedDateTime zonedDateTime) {
-        return RFC_3339_FORMATTER.format(zonedDateTime);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java
deleted file mode 100644
index 7b94a344..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.jacobpeterson.alpaca.util.gson;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.time.ZonedDateTime;
-
-/**
- * {@link GsonUtil} contains utility methods relating to {@link Gson}.
- */
-public class GsonUtil {
-
-    /** A {@link Gson} instance with registered date/time adapters. */
-    public static final Gson GSON = new GsonBuilder()
-            .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter())
-            .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
-            .registerTypeAdapter(LocalTime.class, new LocalTimeAdapter())
-            .enableComplexMapKeySerialization()
-            .setLenient()
-            .create();
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java
deleted file mode 100644
index d1dc5956..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package net.jacobpeterson.alpaca.util.gson;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-
-import java.lang.reflect.Type;
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-
-/**
- * {@link LocalDateAdapter} is a {@link Gson} adapter for {@link LocalDate}s.
- */
-public class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
-
-    @Override
-    public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
-        return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE));
-    }
-
-    @Override
-    public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
-            throws JsonParseException {
-        return LocalDate.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_LOCAL_DATE);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java
deleted file mode 100644
index 960ccda2..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package net.jacobpeterson.alpaca.util.gson;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-
-import java.lang.reflect.Type;
-import java.time.LocalTime;
-import java.time.format.DateTimeFormatter;
-
-/**
- * {@link LocalTimeAdapter} is a {@link Gson} adapter for {@link LocalTime}s.
- */
-public class LocalTimeAdapter implements JsonSerializer<LocalTime>, JsonDeserializer<LocalTime> {
-
-    @Override
-    public JsonElement serialize(LocalTime localTime, Type typeOfSrc, JsonSerializationContext context) {
-        return new JsonPrimitive(localTime.format(DateTimeFormatter.ISO_LOCAL_TIME));
-    }
-
-    @Override
-    public LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
-            throws JsonParseException {
-        return LocalTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_LOCAL_TIME);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java
deleted file mode 100644
index 7f1cf2a7..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package net.jacobpeterson.alpaca.util.gson;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-
-import java.lang.reflect.Type;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-
-/**
- * {@link ZonedDateTimeAdapter} is a {@link Gson} adapter for {@link ZonedDateTime}s.
- */
-public class ZonedDateTimeAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
-
-    @Override
-    public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializationContext context) {
-        return new JsonPrimitive(src.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
-    }
-
-    @Override
-    public ZonedDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
-            throws JsonParseException {
-        return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_DATE_TIME);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java
deleted file mode 100644
index c6f54498..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package net.jacobpeterson.alpaca.util.okhttp;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import okhttp3.MediaType;
-import okhttp3.RequestBody;
-
-/**
- * {@link JSONBodyBuilder} is builder HTTP bodies.
- */
-public class JSONBodyBuilder {
-
-    /** The UTF-8 JSON {@link MediaType}. */
-    public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
-
-    private String bodyJSON;
-    private JsonObject bodyJsonObject;
-
-    /**
-     * Instantiates a new {@link JSONBodyBuilder}.
-     */
-    public JSONBodyBuilder() {
-        bodyJsonObject = new JsonObject();
-    }
-
-    /**
-     * Instantiates a new {@link JSONBodyBuilder}.
-     *
-     * @param bodyJSON a JSON body {@link String} that is used instead of the internal {@link #bodyJsonObject}
-     */
-    public JSONBodyBuilder(String bodyJSON) {
-        this.bodyJSON = bodyJSON;
-    }
-
-    /**
-     * Appends a {@link String} to {@link #bodyJsonObject}.
-     *
-     * @param key   the key
-     * @param value the value
-     */
-    public void appendJSONBodyProperty(String key, String value) {
-        bodyJsonObject.addProperty(key, value);
-    }
-
-    /**
-     * Appends a {@link JsonElement} property to {@link #bodyJsonObject}.
-     *
-     * @param key   the key
-     * @param value the {@link JsonElement} value
-     */
-    public void appendJSONBodyJSONProperty(String key, JsonElement value) {
-        bodyJsonObject.add(key, value);
-    }
-
-    /**
-     * Builds a {@link RequestBody}.
-     *
-     * @return a {@link RequestBody}
-     */
-    public RequestBody build() {
-        return RequestBody.create(bodyJSON != null ? bodyJSON : bodyJsonObject.toString(), JSON_MEDIA_TYPE);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java
deleted file mode 100644
index 50882e47..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package net.jacobpeterson.alpaca.util.properties;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * {@link PropertyUtil} is a util class for all things {@link Properties}.
- */
-public class PropertyUtil {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class);
-
-    public static final Map<String, Properties> CACHED_PROPERTIES = Collections.synchronizedMap(new HashMap<>());
-
-    /**
-     * Gets a string property from a property file. Will try to IO load the properties in the property file if not
-     * cached already.
-     *
-     * @param propertyFile the property file
-     * @param key          the key
-     *
-     * @return the string
-     */
-    public static String getProperty(String propertyFile, String key) {
-        return getProperty(propertyFile, null, key, null);
-    }
-
-    /**
-     * Gets a string property from a property file. Will try to IO load the properties in the property file if not
-     * cached already. If the desired property is not found in the <code>propertyFile</code>, then the
-     * <code>defaultPropertyFile</code> is searched.
-     *
-     * @param propertyFile        the property file
-     * @param defaultPropertyFile the default property file
-     * @param key                 the key
-     *
-     * @return the string
-     */
-    public static String getProperty(String propertyFile, String defaultPropertyFile, String key) {
-        return getProperty(propertyFile, defaultPropertyFile, key, null);
-    }
-
-    /**
-     * Gets a string property from a property file. Will try to IO load the properties in the property file if not
-     * cached already. If the desired property is not found in the <code>propertyFile</code>, then the
-     * <code>defaultPropertyFile</code> is searched, and if it's not there, then <code>defaultValue</code> is returned.
-     *
-     * @param propertyFile        the property file
-     * @param defaultPropertyFile the default property file
-     * @param key                 the key
-     * @param defaultValue        the default value (if the desired property wasn't found, then this is returned)
-     *
-     * @return the string
-     */
-    public static String getProperty(String propertyFile, String defaultPropertyFile, String key, String defaultValue) {
-        Properties properties;
-
-        if (!CACHED_PROPERTIES.containsKey(propertyFile)) {
-            properties = loadPropertyFile(propertyFile, defaultPropertyFile);
-            CACHED_PROPERTIES.put(propertyFile, properties);
-        } else {
-            properties = CACHED_PROPERTIES.get(propertyFile);
-        }
-
-        return properties == null ? defaultValue : properties.getProperty(key, defaultValue);
-    }
-
-    /**
-     * Loads property file {@link Properties}.
-     *
-     * @param propertyFile        the property file name
-     * @param defaultPropertyFile the default property file name
-     *
-     * @return the properties
-     */
-    public synchronized static Properties loadPropertyFile(String propertyFile, String defaultPropertyFile) {
-        ClassLoader classLoader = PropertyUtil.class.getClassLoader();
-        if (classLoader == null) {
-            classLoader = ClassLoader.getSystemClassLoader();
-        }
-
-        // Load the default property file if exists
-        Properties defaultProperties = null;
-        InputStream defaultPropertyStream = classLoader.getResourceAsStream(defaultPropertyFile);
-
-        if (defaultPropertyStream != null) {
-            defaultProperties = new Properties();
-
-            // Load the properties
-            try {
-                defaultProperties.load(defaultPropertyStream);
-                LOGGER.debug("Loaded default properties file: {}", defaultPropertyFile);
-            } catch (IOException exception) {
-                LOGGER.error("Could not load default property file: {}\n{}", defaultPropertyFile, exception);
-            }
-
-            // Close the InputStream
-            try {
-                defaultPropertyStream.close();
-            } catch (IOException exception) {
-                LOGGER.error("Could not close default property file stream: {}\n{}", defaultPropertyFile, exception);
-            }
-
-        } else {
-            LOGGER.warn("No default property file found for: {}", propertyFile);
-        }
-
-        // Load the property file
-        Properties properties = null;
-        InputStream propertyStream = classLoader.getResourceAsStream(propertyFile);
-
-        if (propertyStream != null) {
-            // Add default properties if they were found
-            properties = defaultProperties == null ? new Properties() : new Properties(defaultProperties);
-
-            // Load the properties
-            try {
-                properties.load(propertyStream);
-                LOGGER.info("Loaded properties file: {}", propertyFile);
-            } catch (IOException exception) {
-                LOGGER.error("Could not load property file: {}\n{}", propertyFile, exception);
-            }
-
-            // Close the InputStream
-            try {
-                propertyStream.close();
-            } catch (IOException exception) {
-                LOGGER.error("Could not close property file stream: {}\n{}", propertyFile, exception);
-            }
-        } else {
-            LOGGER.debug("Could not find property file: {}", propertyFile);
-
-            if (defaultProperties != null) {
-                LOGGER.info("Using default properties for: {}", propertyFile);
-                properties = defaultProperties;
-            }
-        }
-
-        return properties;
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java
new file mode 100644
index 00000000..5b28942b
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java
@@ -0,0 +1,38 @@
+package net.jacobpeterson.alpaca.util.sse;
+
+import okhttp3.Response;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link SSEListener} is a listener interface for server-sent events (SSE).
+ *
+ * @param <T> the data type
+ */
+public interface SSEListener<T> {
+
+    /**
+     * Called on SSE open.
+     */
+    void onOpen();
+
+    /**
+     * Called on SSE close.
+     */
+    void onClose();
+
+    /**
+     * Called on SSE error.
+     *
+     * @param throwable the {@link Throwable}
+     * @param response  the {@link Response}
+     */
+    void onError(@Nullable Throwable throwable, @Nullable Response response);
+
+    /**
+     * Called on SSE message received.
+     *
+     * @param message the {@link T} message
+     */
+    void onMessage(@NotNull T message);
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java
new file mode 100644
index 00000000..0bd194d1
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java
@@ -0,0 +1,33 @@
+package net.jacobpeterson.alpaca.util.sse;
+
+import okhttp3.Response;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link SSEListenerAdapter} and adapter for {@link SSEListener}.
+ *
+ * @param <T> the data type
+ */
+public class SSEListenerAdapter<T> implements SSEListener<T> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SSEListenerAdapter.class);
+
+    public void onOpen() {
+        LOGGER.info("SSE connection opened.");
+    }
+
+    public void onClose() {
+        LOGGER.info("SSE connection closed.");
+    }
+
+    public void onError(@Nullable Throwable throwable, @Nullable Response response) {
+        LOGGER.error("SSE connection error! response={}", response, throwable);
+    }
+
+    public void onMessage(@NotNull T message) {
+        LOGGER.info("SSE message received: message={}", message);
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java
new file mode 100644
index 00000000..65e5b96e
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java
@@ -0,0 +1,36 @@
+package net.jacobpeterson.alpaca.util.sse;
+
+import okhttp3.sse.EventSource;
+
+/**
+ * {@link SSERequest} is a {@link EventSource} wrapper class.
+ */
+public class SSERequest {
+
+    private final EventSource eventSource;
+
+    /**
+     * Instantiates a new {@link SSERequest}.
+     *
+     * @param eventSource the {@link EventSource}
+     */
+    public SSERequest(EventSource eventSource) {
+        this.eventSource = eventSource;
+    }
+
+    /**
+     * Closes the internal {@link EventSource}.
+     */
+    public void close() {
+        eventSource.cancel();
+    }
+
+    /**
+     * Gets the internal {@link EventSource}.
+     *
+     * @return the {@link EventSource}
+     */
+    public EventSource getInternalEventSource() {
+        return eventSource;
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java
index 19dee225..d6f920c2 100644
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java
@@ -1,6 +1,5 @@
 package net.jacobpeterson.alpaca.websocket;
 
-import net.jacobpeterson.alpaca.util.okhttp.WebsocketStateListener;
 import okhttp3.HttpUrl;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
@@ -12,21 +11,17 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.time.Duration;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.ForkJoinPool.commonPool;
 
 /**
  * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca.
- *
- * @param <L> the {@link AlpacaWebsocketMessageListener} type parameter
- * @param <T> the 'message type' type parameter
- * @param <M> the 'message' type parameter
  */
-public abstract class AlpacaWebsocket<T, M, L extends AlpacaWebsocketMessageListener<T, M>> extends WebSocketListener
-        implements AlpacaWebsocketInterface<L> {
+public abstract class AlpacaWebsocket extends WebSocketListener implements AlpacaWebsocketInterface {
 
     /**
      * Defines a websocket normal closure code.
@@ -42,32 +37,29 @@ public abstract class AlpacaWebsocket<T, M, L extends AlpacaWebsocketMessageList
      */
     public static final String WEBSOCKET_NORMAL_CLOSURE_MESSAGE = "Normal closure";
 
-    /** Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}. */
+    /**
+     * Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}.
+     */
     public static int MAX_RECONNECT_ATTEMPTS = 5;
 
     /**
-     * Defines the millisecond sleep interval between reconnection attempts made by an {@link AlpacaWebsocket}.
+     * Defines the sleep interval {@link Duration} between reconnection attempts made by an {@link AlpacaWebsocket}.
      */
-    public static int RECONNECT_SLEEP_INTERVAL = 1000;
+    public static Duration RECONNECT_SLEEP_INTERVAL = Duration.ofSeconds(1);
 
     private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocket.class);
 
     protected final OkHttpClient okHttpClient;
     protected final HttpUrl websocketURL;
     protected final String websocketName;
-    protected final String keyID;
-    protected final String secretKey;
-    protected final String oAuthToken;
-    protected final boolean useOAuth;
-
-    protected L listener;
-    protected WebsocketStateListener websocketStateListener;
-    protected WebSocket websocket;
+    protected AlpacaWebsocketStateListener alpacaWebsocketStateListener;
+    private WebSocket websocket;
     protected boolean connected;
     protected boolean authenticated;
     protected CompletableFuture<Boolean> authenticationMessageFuture;
     protected boolean intentionalClose;
     protected int reconnectAttempts;
+
     protected boolean automaticallyReconnect;
 
     /**
@@ -76,12 +68,8 @@ public abstract class AlpacaWebsocket<T, M, L extends AlpacaWebsocketMessageList
      * @param okHttpClient  the {@link OkHttpClient}
      * @param websocketURL  the websocket {@link HttpUrl}
      * @param websocketName the websocket name
-     * @param keyID         the key ID
-     * @param secretKey     the secret key
-     * @param oAuthToken    the OAuth token
      */
-    public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName,
-            String keyID, String secretKey, String oAuthToken) {
+    protected AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName) {
         checkNotNull(okHttpClient);
         checkNotNull(websocketURL);
         checkNotNull(websocketName);
@@ -89,10 +77,6 @@ public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String w
         this.okHttpClient = okHttpClient;
         this.websocketURL = websocketURL;
         this.websocketName = websocketName;
-        this.keyID = keyID;
-        this.secretKey = secretKey;
-        this.oAuthToken = oAuthToken;
-        useOAuth = oAuthToken != null;
 
         automaticallyReconnect = true;
     }
@@ -100,7 +84,7 @@ public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String w
     @Override
     public void connect() {
         if (!isConnected()) {
-            Request websocketRequest = new Request.Builder()
+            final Request websocketRequest = new Request.Builder()
                     .url(websocketURL)
                     .get()
                     .build();
@@ -131,56 +115,51 @@ public boolean isAuthenticated() {
     @Override
     public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
         connected = true;
-
-        LOGGER.info("{} websocket opened.", websocketName);
-        LOGGER.debug("{} websocket response: {}", websocketName, response);
-
-        // Call 'onConnection' or 'onReconnection' async to avoid any potential dead-locking since this is called
+        LOGGER.info("{} websocket response: response={}", websocketName, response);
+        // Call 'onConnection' or 'onReconnection' async to avoid any potential deadlocking since this is called
         // in sync with 'onMessage' in OkHttp's 'WebSocketListener'
-        ForkJoinPool.commonPool().execute(() -> {
+        commonPool().execute(() -> {
             if (reconnectAttempts > 0) {
-                reconnectAttempts = 0;
                 onReconnection();
             } else {
                 onConnection();
             }
         });
-
-        if (websocketStateListener != null) {
-            websocketStateListener.onOpen(response);
+        if (alpacaWebsocketStateListener != null) {
+            alpacaWebsocketStateListener.onOpen(response);
         }
     }
 
     @Override
     public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
         connected = false;
-
         if (intentionalClose) {
-            LOGGER.info("{} websocket closed.", websocketName);
-            LOGGER.debug("Close code: {}, Reason: {}", code, reason);
+            LOGGER.info("{} websocket closed. code={}, reason={}", websocketName, code, reason);
             cleanupState();
         } else {
-            LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason);
+            LOGGER.error("{} websocket closed unintentionally! code={}, reason={}", websocketName, code, reason);
             handleReconnectionAttempt();
         }
-
-        if (websocketStateListener != null) {
-            websocketStateListener.onClosed(code, reason);
+        if (alpacaWebsocketStateListener != null) {
+            alpacaWebsocketStateListener.onClosed(code, reason);
         }
     }
 
     @Override
     public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) {
-        LOGGER.error("{} websocket failure!", websocketName, cause);
+        if (intentionalClose) {
+            onClosed(webSocket, WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE);
+            return;
+        }
 
+        LOGGER.error("{} websocket failure!", websocketName, cause);
         // A websocket failure occurs when either there is a connection failure or when the client throws
-        // an Exception when receiving a message. In either case, OkHttp will silently close the websocket
-        // connection, so try to reopen it.
+        // an exception when receiving a message. In either case, OkHttp will close the websocket connection,
+        // so try to reopen it.
         connected = false;
         handleReconnectionAttempt();
-
-        if (websocketStateListener != null) {
-            websocketStateListener.onFailure(cause);
+        if (alpacaWebsocketStateListener != null) {
+            alpacaWebsocketStateListener.onFailure(cause);
         }
     }
 
@@ -191,20 +170,16 @@ private void handleReconnectionAttempt() {
         if (!automaticallyReconnect) {
             return;
         }
-
         if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
-            LOGGER.info("Attempting to reconnect {} websocket in {} milliseconds...",
-                    websocketName, RECONNECT_SLEEP_INTERVAL);
-
+            LOGGER.info("Attempting to reconnect {} websocket in {} seconds... (attempt {} of {})",
+                    websocketName, RECONNECT_SLEEP_INTERVAL.toSeconds(), reconnectAttempts + 1, MAX_RECONNECT_ATTEMPTS);
             reconnectAttempts++;
-
-            ForkJoinPool.commonPool().execute(() -> {
+            commonPool().execute(() -> {
                 try {
-                    Thread.sleep(RECONNECT_SLEEP_INTERVAL);
-                } catch (InterruptedException ignored) {
+                    Thread.sleep(RECONNECT_SLEEP_INTERVAL.toMillis());
+                } catch (InterruptedException interruptedException) {
                     return;
                 }
-
                 connect();
             });
         } else {
@@ -214,7 +189,7 @@ private void handleReconnectionAttempt() {
     }
 
     /**
-     * Cleans up this instances state variables.
+     * Cleans up this instance's state variables.
      */
     protected void cleanupState() {
         websocket = null;
@@ -228,6 +203,19 @@ protected void cleanupState() {
         reconnectAttempts = 0;
     }
 
+    /**
+     * Sends a message to the underlying {@link #websocket}.
+     *
+     * @param message the message
+     */
+    protected void sendWebsocketMessage(String message) {
+        if (!isConnected()) {
+            throw new IllegalStateException("This websocket must be connected before send a message!");
+        }
+        LOGGER.trace("Websocket message sent: {}", message);
+        websocket.send(message);
+    }
+
     /**
      * Called asynchronously when a websocket connection is made.
      */
@@ -248,62 +236,33 @@ public Future<Boolean> getAuthorizationFuture() {
         if (authenticationMessageFuture == null) {
             authenticationMessageFuture = new CompletableFuture<>();
         }
-
         return authenticationMessageFuture;
     }
 
-    /**
-     * Calls the {@link AlpacaWebsocketMessageListener}.
-     *
-     * @param messageType the message type
-     * @param message     the message
-     */
-    protected void callListener(T messageType, M message) {
-        try {
-            listener.onMessage(messageType, message);
-        } catch (Exception exception) {
-            LOGGER.error("{} listener threw exception!", websocketName, exception);
-        }
+    @Override
+    public void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener) {
+        this.alpacaWebsocketStateListener = alpacaWebsocketStateListener;
     }
 
     @Override
-    public void setListener(L listener) {
-        this.listener = listener;
+    public boolean doesAutomaticallyReconnect() {
+        return automaticallyReconnect;
     }
 
-    /**
-     * Gets {@link #websocketStateListener}.
-     *
-     * @return the {@link WebsocketStateListener}
-     */
-    public WebsocketStateListener getWebsocketStateListener() {
-        return websocketStateListener;
+    @Override
+    public void setAutomaticallyReconnect(boolean automaticallyReconnect) {
+        this.automaticallyReconnect = automaticallyReconnect;
     }
 
-    /**
-     * Sets {@link #websocketStateListener}.
-     *
-     * @param websocketStateListener an {@link WebsocketStateListener}
-     */
-    public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) {
-        this.websocketStateListener = websocketStateListener;
+    public OkHttpClient getOkHttpClient() {
+        return okHttpClient;
     }
 
-    /**
-     * Gets {@link #automaticallyReconnect}.
-     *
-     * @return a boolean
-     */
-    public boolean doesAutomaticallyReconnect() {
-        return automaticallyReconnect;
+    public HttpUrl getWebsocketURL() {
+        return websocketURL;
     }
 
-    /**
-     * Sets {@link #automaticallyReconnect}.
-     *
-     * @param automaticallyReconnect the boolean
-     */
-    public void setAutomaticallyReconnect(boolean automaticallyReconnect) {
-        this.automaticallyReconnect = automaticallyReconnect;
+    public WebSocket getWebsocket() {
+        return websocket;
     }
 }
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java
index c508e799..48adfb4d 100644
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java
@@ -7,10 +7,8 @@
 
 /**
  * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets.
- *
- * @param <L> the {@link AlpacaWebsocketMessageListener} type parameter
  */
-public interface AlpacaWebsocketInterface<L extends AlpacaWebsocketMessageListener<?, ?>> {
+public interface AlpacaWebsocketInterface {
 
     /**
      * Connects this Websocket.
@@ -23,53 +21,74 @@ public interface AlpacaWebsocketInterface<L extends AlpacaWebsocketMessageListen
     void disconnect();
 
     /**
-     * Returns true if this websocket is connected.
+     * Returns <code>true</code> if this websocket is connected, <code>false</code> otherwise.
      *
      * @return a boolean
      */
     boolean isConnected();
 
     /**
-     * Returns true if this websocket is authenticated.
+     * Returns <code>true</code> if this websocket is authenticated, <code>false</code> otherwise.
      *
      * @return a boolean
      */
     boolean isAuthenticated();
 
+    /**
+     * Returns <code>true</code> if {@link #isConnected()} and {@link #isAuthenticated()}, <code>false</code>
+     * otherwise.
+     *
+     * @return a boolean
+     */
+    default boolean isValid() {
+        return isConnected() && isAuthenticated();
+    }
+
     /**
      * Gets a {@link Boolean} {@link Future} that completes when an authentication message that is received after a new
      * websocket connection indicates successful authentication.
+     * <br>
+     * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely
+     * never complete.
      *
      * @return a {@link Boolean} {@link Future}
      */
     Future<Boolean> getAuthorizationFuture();
 
     /**
-     * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. This will timeout after 10 seconds
-     * and then return <code>false</code>.
+     * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when <code>timeout</code>
+     * time has elapsed, then this will return <code>false</code>.
+     *
+     * @param timeout the timeout time
+     * @param unit    the timeout {@link TimeUnit}
      *
      * @return a boolean
      */
-    default boolean waitForAuthorization() {
+    default boolean waitForAuthorization(long timeout, TimeUnit unit) {
         try {
-            return getAuthorizationFuture().get(10, TimeUnit.SECONDS);
+            return getAuthorizationFuture().get(timeout, unit);
         } catch (InterruptedException | ExecutionException | TimeoutException ignored) {}
         return false;
     }
 
     /**
-     * True if {@link #isConnected()} and {@link #isAuthenticated()}.
+     * Sets the {@link AlpacaWebsocketStateListener}.
+     *
+     * @param alpacaWebsocketStateListener the {@link AlpacaWebsocketStateListener}
+     */
+    void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener);
+
+    /**
+     * Returns <code>true</code> if this websocket automatically reconnects, <code>false</code> otherwise.
      *
      * @return a boolean
      */
-    default boolean isValid() {
-        return isConnected() && isAuthenticated();
-    }
+    boolean doesAutomaticallyReconnect();
 
     /**
-     * Sets the {@link AlpacaWebsocketMessageListener} for this {@link AlpacaWebsocketInterface}.
+     * Sets whether to automatically reconnect and reauthenticate on a websocket failure. <code>true</code> by default.
      *
-     * @param listener the {@link AlpacaWebsocketMessageListener}
+     * @param automaticallyReconnect <code>true</code> to automatically reconnect, <code>false</code> otherwise
      */
-    void setListener(L listener);
+    void setAutomaticallyReconnect(boolean automaticallyReconnect);
 }
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java
deleted file mode 100644
index da55535c..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.jacobpeterson.alpaca.websocket;
-
-/**
- * {@link AlpacaWebsocketMessageListener} defines a listener interface for messages from an {@link AlpacaWebsocket}
- * instances.
- *
- * @param <T> the 'message type' type parameter
- * @param <M> the 'message' type parameter
- */
-@FunctionalInterface
-public interface AlpacaWebsocketMessageListener<T, M> {
-
-    /**
-     * Called when a message is received.
-     *
-     * @param messageType the message type
-     * @param message     the message
-     */
-    void onMessage(T messageType, M message);
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java
similarity index 73%
rename from src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java
rename to src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java
index 38f91d70..2667c2ca 100644
--- a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java
@@ -1,12 +1,13 @@
-package net.jacobpeterson.alpaca.util.okhttp;
+package net.jacobpeterson.alpaca.websocket;
 
 import okhttp3.Response;
 import okhttp3.WebSocket;
 
 /**
- * {@link WebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket} instance.
+ * {@link AlpacaWebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket}
+ * instance.
  */
-public interface WebsocketStateListener {
+public interface AlpacaWebsocketStateListener {
 
     /**
      * Called when the {@link WebSocket} is connected.
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java
deleted file mode 100644
index c1138209..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.jacobpeterson.alpaca.websocket.marketdata;
-
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType;
-import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener;
-
-/**
- * {@link MarketDataListener} defines a listener interface for {@link MarketDataWebsocket} messages.
- */
-public interface MarketDataListener extends AlpacaWebsocketMessageListener<MarketDataMessageType, MarketDataMessage> {
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java
index ad189169..fbb7dca9 100644
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java
@@ -1,19 +1,10 @@
 package net.jacobpeterson.alpaca.websocket.marketdata;
 
-import com.google.common.collect.Iterables;
-import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.bar.BarMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.ErrorMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SubscriptionsMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SuccessMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.quote.QuoteMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.trade.TradeMessage;
-import net.jacobpeterson.alpaca.model.properties.DataAPIType;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.ErrorMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessage;
 import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket;
 import okhttp3.HttpUrl;
 import okhttp3.OkHttpClient;
@@ -22,81 +13,64 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
+import java.util.function.Function;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Predicates.not;
-import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON;
+import static com.google.common.collect.Sets.difference;
+import static com.google.gson.JsonParser.parseString;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType.AUTHENTICATED;
+import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson;
 
 /**
- * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link
- * MarketDataWebsocketInterface} interface for
- * <a href="https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/">Real-time
- * Market Data</a>
+ * {@link MarketDataWebsocket} is an abstract {@link AlpacaWebsocket} implementation for
+ * <a href="https://docs.alpaca.markets/docs/streaming-market-data">Market Data Streaming</a>.
+ *
+ * @param <T> the 'message type' type
+ * @param <S> the 'subscription message' type
+ * @param <L> the 'listener' type
  */
-public class MarketDataWebsocket extends AlpacaWebsocket<MarketDataMessageType, MarketDataMessage, MarketDataListener>
+public abstract class MarketDataWebsocket<T, S extends MarketDataMessage, L> extends AlpacaWebsocket
         implements MarketDataWebsocketInterface {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class);
-    private static final String MESSAGE_TYPE_ELEMENT_KEY = "T";
-    private static final List<String> AUTH_FAILURE_MESSAGES = Arrays.asList(
-            "auth failed",
-            "auth timeout",
-            "not authenticated");
-    private static final List<MarketDataMessageType> SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES = Arrays.asList(
-            MarketDataMessageType.TRADE,
-            MarketDataMessageType.QUOTE,
-            MarketDataMessageType.BAR);
+    private static final Set<String> AUTH_FAILURE_MESSAGES = Set.of("auth failed", "auth timeout", "not authenticated");
 
-    /**
-     * Creates a {@link HttpUrl} for {@link MarketDataWebsocket} with the given <code>dataAPIType</code>.
-     *
-     * @param dataAPIType the {@link DataAPIType}
-     *
-     * @return a {@link HttpUrl}
-     */
-    private static HttpUrl createWebsocketURL(DataAPIType dataAPIType) {
-        return new HttpUrl.Builder()
-                .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, but "https" works fine
-                .host("stream.data.alpaca.markets")
-                .addPathSegment("v2")
-                .addPathSegment(dataAPIType.toString())
-                .build();
-    }
-
-    private final Set<MarketDataMessageType> listenedMarketDataMessageTypes;
-    private final Set<String> subscribedTrades;
-    private final Set<String> subscribedQuotes;
-    private final Set<String> subscribedBars;
+    protected final String authKey;
+    protected final String authSecret;
+    protected final Class<T> messageTypeClass;
+    protected final Class<S> subscriptionsMessageClass;
+    protected S subscriptionsMessage;
+    protected L listener;
 
     /**
      * Instantiates a new {@link MarketDataWebsocket}.
      *
-     * @param okHttpClient the {@link OkHttpClient}
-     * @param dataAPIType  the {@link DataAPIType}
-     * @param keyID        the key ID
-     * @param secretKey    the secret key
+     * @param okHttpClient                the {@link OkHttpClient}
+     * @param websocketURL                the websocket {@link HttpUrl}
+     * @param websocketMarketDataTypeName the websocket market data type name {@link String}
+     * @param traderKeyID                 the trader key ID
+     * @param traderSecretKey             the trader secret key
+     * @param brokerAPIKey                the broker API key
+     * @param brokerAPISecret             the broker API secret
+     * @param messageTypeClass            the {@link T} message type {@link Class}
+     * @param subscriptionsMessageClass   the {@link S} subscription message {@link Class}
      */
-    public MarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType,
-            String keyID, String secretKey) {
-        super(okHttpClient, createWebsocketURL(dataAPIType), "Market Data", keyID, secretKey, null);
-
-        listenedMarketDataMessageTypes = new HashSet<>();
-        subscribedTrades = new HashSet<>();
-        subscribedQuotes = new HashSet<>();
-        subscribedBars = new HashSet<>();
+    protected MarketDataWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketMarketDataTypeName,
+            String traderKeyID, String traderSecretKey, String brokerAPIKey, String brokerAPISecret,
+            Class<T> messageTypeClass, Class<S> subscriptionsMessageClass) {
+        super(okHttpClient, websocketURL, websocketMarketDataTypeName + " Market Data");
+        final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null;
+        this.authKey = traderKeysGiven ? traderKeyID : brokerAPIKey;
+        this.authSecret = traderKeysGiven ? traderSecretKey : brokerAPISecret;
+        this.messageTypeClass = messageTypeClass;
+        this.subscriptionsMessageClass = subscriptionsMessageClass;
     }
 
     @Override
     protected void cleanupState() {
         super.cleanupState();
-
-        listenedMarketDataMessageTypes.clear();
+        subscriptionsMessage = null;
     }
 
     @Override
@@ -107,255 +81,118 @@ protected void onConnection() {
     @Override
     protected void onReconnection() {
         sendAuthenticationMessage();
-        if (waitForAuthorization()) {
-            subscribeToControl(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class));
-            subscribe(subscribedTrades, subscribedQuotes, subscribedBars);
+        if (waitForAuthorization(5, SECONDS) && subscriptionsMessage != null) {
+            sendSubscriptionMessage(subscriptionsMessage, true);
         }
     }
 
     @Override
     protected void sendAuthenticationMessage() {
-        // Ensures that 'authenticationMessageFuture' exists
+        // Ensure that the authorization Future exists
         getAuthorizationFuture();
 
-        /* Format of message is:
-         * {
-         *   "action": "auth",
-         *   "key": "{APCA-API-KEY-ID}",
-         *   "secret": "{APCA-API-SECRET-KEY}"
-         * }
-         */
-
-        JsonObject authObject = new JsonObject();
+        final JsonObject authObject = new JsonObject();
         authObject.addProperty("action", "auth");
-        authObject.addProperty("key", keyID);
-        authObject.addProperty("secret", secretKey);
+        authObject.addProperty("key", authKey);
+        authObject.addProperty("secret", authSecret);
 
         LOGGER.info("{} websocket sending authentication message...", websocketName);
-        websocket.send(authObject.toString());
+        sendWebsocketMessage(authObject.toString());
     }
 
-    // This websocket uses string frames and not binary frames.
     @Override
-    public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) {
-        JsonElement messageElement = JsonParser.parseString(message);
-        checkState(messageElement instanceof JsonArray, "Message must be a JsonArray! Received: %s", messageElement);
-
-        JsonArray messageArray = messageElement.getAsJsonArray();
-        for (JsonElement arrayElement : messageArray) {
-            checkState(arrayElement instanceof JsonObject,
-                    "'arrayElement' must be a JsonObject! Received: %s", arrayElement);
-
-            JsonObject messageObject = arrayElement.getAsJsonObject();
-            MarketDataMessageType marketDataMessageType = GSON.fromJson(
-                    messageObject.get(MESSAGE_TYPE_ELEMENT_KEY), MarketDataMessageType.class);
-            checkNotNull(marketDataMessageType, "MarketDataMessageType not found in message: %s", messageObject);
-
-            MarketDataMessage marketDataMessage;
-            switch (marketDataMessageType) {
-                case SUCCESS:
-                    marketDataMessage = GSON.fromJson(messageObject, SuccessMessage.class);
-                    LOGGER.debug("{}", marketDataMessage);
-
-                    if (isSuccessMessageAuthenticated((SuccessMessage) marketDataMessage)) {
-                        LOGGER.info("{} websocket authenticated.", websocketName);
-
-                        authenticated = true;
-
-                        if (authenticationMessageFuture != null) {
-                            authenticationMessageFuture.complete(true);
-                        }
+    public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { // Text framing
+        LOGGER.trace("Websocket message received: {}", message);
+
+        // Loop through message array and handle each message according to its type
+        for (JsonElement arrayElement : parseString(message).getAsJsonArray()) {
+            final JsonObject messageObject = arrayElement.getAsJsonObject();
+            final T messageType = getGson().fromJson(messageObject.get("T"), messageTypeClass);
+            if (isSuccessMessageType(messageType)) {
+                final SuccessMessage successMessage = getGson().fromJson(messageObject, SuccessMessage.class);
+                if (successMessage.getMessageType() == AUTHENTICATED) {
+                    LOGGER.info("{} websocket authenticated.", websocketName);
+                    authenticated = true;
+                    if (authenticationMessageFuture != null) {
+                        authenticationMessageFuture.complete(true);
                     }
-                    break;
-                case ERROR:
-                    marketDataMessage = GSON.fromJson(messageObject, ErrorMessage.class);
-
-                    if (isErrorMessageAuthFailure((ErrorMessage) marketDataMessage) &&
-                            authenticationMessageFuture != null) {
-                        LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, marketDataMessage);
-
-                        authenticated = false;
-
-                        if (authenticationMessageFuture != null) {
-                            authenticationMessageFuture.complete(false);
-                        }
-                    } else {
-                        LOGGER.error("{} websocket error message: {}", websocketName, marketDataMessage);
-                    }
-                    break;
-                case SUBSCRIPTION:
-                    marketDataMessage = GSON.fromJson(messageObject, SubscriptionsMessage.class);
-                    LOGGER.debug("{}", marketDataMessage);
-
-                    // Update 'listenedMarketDataMessageTypes' and the associated subscribed symbols lists
-                    SubscriptionsMessage subscriptionsMessage = (SubscriptionsMessage) marketDataMessage;
-                    handleSubscriptionMessageList(MarketDataMessageType.TRADE, subscriptionsMessage.getTrades(),
-                            subscribedTrades);
-                    handleSubscriptionMessageList(MarketDataMessageType.QUOTE, subscriptionsMessage.getQuotes(),
-                            subscribedQuotes);
-                    handleSubscriptionMessageList(MarketDataMessageType.BAR, subscriptionsMessage.getBars(),
-                            subscribedBars);
-                    break;
-                case TRADE:
-                    marketDataMessage = GSON.fromJson(messageObject, TradeMessage.class);
-                    break;
-                case QUOTE:
-                    marketDataMessage = GSON.fromJson(messageObject, QuoteMessage.class);
-                    break;
-                case BAR:
-                    marketDataMessage = GSON.fromJson(messageObject, BarMessage.class);
-                    break;
-                default:
-                    LOGGER.error("Message type {} not implemented!", marketDataMessageType);
-                    continue;
-            }
-
-            if (listenedMarketDataMessageTypes.contains(marketDataMessageType)) {
-                callListener(marketDataMessageType, marketDataMessage);
+                }
+            } else if (isErrorMessageType(messageType)) {
+                final ErrorMessage errorMessage = getGson().fromJson(messageObject, ErrorMessage.class);
+                if (AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage()) && authenticationMessageFuture != null) {
+                    authenticated = false;
+                    authenticationMessageFuture.complete(false);
+                    throw new RuntimeException(websocketName + " websocket authentication failed!");
+                } else {
+                    throw new RuntimeException(websocketName + " websocket error! Message: " + errorMessage);
+                }
+            } else if (isSubscriptionMessageType(messageType)) {
+                subscriptionsMessage = getGson().fromJson(messageObject, subscriptionsMessageClass);
+            } else if (listener != null) {
+                callListenerWithMessage(messageType, messageObject);
             }
         }
     }
 
     /**
-     * Returns true if {@link SuccessMessage#getMessage()} equals "authenticated".
-     *
-     * @param successMessage the {@link SuccessMessage}
+     * Sets the websocket stream's subscriptions for a specific message type.
      *
-     * @return a boolean
+     * @param previousSubscriptions           the previous subscriptions symbol {@link Set}
+     * @param newSubscriptions                the new subscriptions symbol {@link Set}
+     * @param subscriptionUpdateObjectCreator the subscription update object creator {@link Function}
      */
-    private boolean isSuccessMessageAuthenticated(SuccessMessage successMessage) {
-        return successMessage.getMessage().equalsIgnoreCase("authenticated");
+    protected void setSubscriptions(@NotNull Set<String> previousSubscriptions,
+            @NotNull Set<String> newSubscriptions, @NotNull Function<Set<String>, S> subscriptionUpdateObjectCreator) {
+        // Unsubscribe from previous subscriptions
+        final Set<String> unsubscribeSet = difference(previousSubscriptions, newSubscriptions);
+        if (!unsubscribeSet.isEmpty()) {
+            sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(unsubscribeSet), false);
+        }
+        // Subscribe to new subscriptions
+        final Set<String> subscribeSet = difference(newSubscriptions, previousSubscriptions);
+        if (!subscribeSet.isEmpty()) {
+            sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(subscribeSet), true);
+        }
+    }
+
+    private void sendSubscriptionMessage(S subscriptionsMessage, boolean subscribe) {
+        final JsonObject subscribeObject = getGson().toJsonTree(subscriptionsMessage).getAsJsonObject();
+        subscribeObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe");
+        sendWebsocketMessage(getGson().toJson(subscribeObject));
     }
 
     /**
-     * Handles a {@link SubscriptionsMessage} for updating {@link #listenedMarketDataMessageTypes} and returns a {@link
-     * List} of currently subscribe symbols or <code>null</code>.
+     * Whether the given <code>messageType</code> is "success".
+     *
+     * @param messageType the message type
      *
-     * @param marketDataMessageType   the {@link MarketDataMessageType}
-     * @param newSubscribedSymbols    the new subscribed symbols from {@link SubscriptionsMessage}
-     * @param currentSubscribeSymbols the currently subscribe symbols {@link Set}
+     * @return a boolean
      */
-    private void handleSubscriptionMessageList(MarketDataMessageType marketDataMessageType,
-            Collection<String> newSubscribedSymbols, Set<String> currentSubscribeSymbols) {
-        if (newSubscribedSymbols != null && !newSubscribedSymbols.isEmpty()) {
-            listenedMarketDataMessageTypes.add(marketDataMessageType);
-            currentSubscribeSymbols.clear();
-            currentSubscribeSymbols.addAll(newSubscribedSymbols);
-        } else {
-            listenedMarketDataMessageTypes.remove(marketDataMessageType);
-            currentSubscribeSymbols.clear();
-        }
-    }
+    protected abstract boolean isSuccessMessageType(T messageType);
 
     /**
-     * Returns true if {@link ErrorMessage#getMessage()} is any of {@link #AUTH_FAILURE_MESSAGES}.
+     * Whether the given <code>messageType</code> is "error".
      *
-     * @param errorMessage the {@link ErrorMessage}
+     * @param messageType the message type
      *
      * @return a boolean
      */
-    private boolean isErrorMessageAuthFailure(ErrorMessage errorMessage) {
-        return AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage().toLowerCase());
-    }
-
-    @Override
-    public void subscribeToControl(MarketDataMessageType... marketDataMessageTypes) {
-        if (marketDataMessageTypes == null) {
-            return;
-        }
-
-        Arrays.stream(marketDataMessageTypes)
-                .filter(not(SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES::contains))
-                .forEach(listenedMarketDataMessageTypes::add);
-    }
-
-    @Override
-    public void subscribe(Collection<String> tradeSymbols, Collection<String> quoteSymbols,
-            Collection<String> barSymbols) {
-        sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, true);
-    }
-
-    @Override
-    public void unsubscribe(Collection<String> tradeSymbols, Collection<String> quoteSymbols,
-            Collection<String> barSymbols) {
-        sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, false);
-    }
+    protected abstract boolean isErrorMessageType(T messageType);
 
     /**
-     * Sends a subscription update request.
+     * Whether the given <code>messageType</code> is "subscription".
      *
-     * @param tradeSymbols a {@link Collection} of symbols to update for trades or <code>null</code> for no change
-     * @param quoteSymbols a {@link Collection} of symbols to update for quotes or <code>null</code> for no change
-     * @param barSymbols   a {@link Collection} of symbols to update for bars or <code>null</code> for no change
-     * @param subscribe    true to subscribe, false to unsubscribe
+     * @param messageType the message type
+     *
+     * @return a boolean
      */
-    private void sendSubscriptionUpdate(Collection<String> tradeSymbols, Collection<String> quoteSymbols,
-            Collection<String> barSymbols, boolean subscribe) {
-        if (!isConnected()) {
-            connect();
-
-            if (!waitForAuthorization()) {
-                LOGGER.error("Not subscribing to streams due to unauthorized {} websocket.", websocketName);
-                return;
-            }
-        }
-
-        /* Format of message is:
-         * {
-         *   "action": "subscribe",
-         *   "trades": ["AAPL"],
-         *   "quotes": ["AMD", "CLDR"],
-         *   "bars": ["*"]
-         * }
-         */
-
-        JsonObject subscriptionUpdateObject = new JsonObject();
-        subscriptionUpdateObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe");
-
-        addSubscriptionUpdateList(subscriptionUpdateObject, "trades", tradeSymbols);
-        addSubscriptionUpdateList(subscriptionUpdateObject, "quotes", quoteSymbols);
-        addSubscriptionUpdateList(subscriptionUpdateObject, "bars", barSymbols);
-
-        boolean updateExists = subscriptionUpdateObject.size() > 1;
-        if (updateExists) {
-            websocket.send(subscriptionUpdateObject.toString());
-            LOGGER.info("Requested subscriptions update: {}.", subscriptionUpdateObject);
-        }
-    }
+    protected abstract boolean isSubscriptionMessageType(T messageType);
 
     /**
-     * Adds <code>symbols</code> to <code>subscriptionUpdateObject</code> with <code>elementKey</code> if not empty.
+     * Calls the {@link #listener} with a {@link MarketDataMessage} from a {@link JsonObject}.
      *
-     * @param subscriptionUpdateObject the subscription update {@link JsonObject}
-     * @param elementKey               the element key
-     * @param symbols                  the symbols {@link Collection}
+     * @param messageType   the message type
+     * @param messageObject the message {@link JsonObject}
      */
-    private void addSubscriptionUpdateList(JsonObject subscriptionUpdateObject, String elementKey,
-            Collection<String> symbols) {
-        if (symbols != null && !symbols.isEmpty()) {
-            JsonArray symbolArray = new JsonArray();
-            symbols.forEach(symbolArray::add);
-            subscriptionUpdateObject.add(elementKey, symbolArray);
-        }
-    }
-
-    @Override
-    public Collection<MarketDataMessageType> subscribedControls() {
-        return new HashSet<>(listenedMarketDataMessageTypes);
-    }
-
-    @Override
-    public Collection<String> subscribedTrades() {
-        return new HashSet<>(subscribedTrades);
-    }
-
-    @Override
-    public Collection<String> subscribedQuotes() {
-        return new HashSet<>(subscribedQuotes);
-    }
-
-    @Override
-    public Collection<String> subscribedBars() {
-        return new HashSet<>(subscribedBars);
-    }
+    protected abstract void callListenerWithMessage(T messageType, JsonObject messageObject);
 }
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java
index 7c7d4d90..989389eb 100644
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java
@@ -1,87 +1,8 @@
 package net.jacobpeterson.alpaca.websocket.marketdata;
 
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.ErrorMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SubscriptionsMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SuccessMessage;
-import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType;
 import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface;
 
-import java.util.Collection;
-
 /**
- * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link MarketDataWebsocket}.
+ * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link MarketDataWebsocket}.
  */
-public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface<MarketDataListener> {
-
-    /**
-     * Subscribe to a specific control {@link MarketDataMessage}. That is, {@link ErrorMessage}, {@link
-     * SubscriptionsMessage}, or {@link SuccessMessage}.
-     *
-     * @param marketDataMessageTypes array of any of: {@link MarketDataMessageType#SUCCESS}, {@link
-     *                               MarketDataMessageType#ERROR}, or {@link MarketDataMessageType#SUBSCRIPTION}
-     */
-    void subscribeToControl(MarketDataMessageType... marketDataMessageTypes);
-
-    /**
-     * Subscribes to trades, quotes, or bars according to the given {@link Collection} of symbols.
-     * <br>
-     * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to subscribe to
-     * ALL available symbols.
-     * <br>
-     * Note that this will call {@link MarketDataWebsocketInterface#connect()} and {@link
-     * MarketDataWebsocketInterface#waitForAuthorization()} if {@link MarketDataWebsocketInterface#isConnected()}
-     * returns false.
-     *
-     * @param tradeSymbols a {@link Collection} of symbols to subscribe to trades or <code>null</code> for no change
-     * @param quoteSymbols a {@link Collection} of symbols to subscribe to quotes or <code>null</code> for no change
-     * @param barSymbols   a {@link Collection} of symbols to subscribe to bars or <code>null</code> for no change
-     *
-     * @see #unsubscribe(Collection, Collection, Collection)
-     */
-    void subscribe(Collection<String> tradeSymbols, Collection<String> quoteSymbols, Collection<String> barSymbols);
-
-    /**
-     * Unsubscribes from trades, quotes, or bars according to the given {@link Collection} of symbols.
-     * <br>
-     * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to unsubscribe
-     * from a previously subscribed wildcard.
-     *
-     * @param tradeSymbols a {@link Collection} of symbols to unsubscribe from trades or <code>null</code> for no
-     *                     change
-     * @param quoteSymbols a {@link Collection} of symbols to unsubscribe from quotes or <code>null</code> for no
-     *                     change
-     * @param barSymbols   a {@link Collection} of symbols to unsubscribe from bars or <code>null</code> for no change
-     *
-     * @see #subscribe(Collection, Collection, Collection)
-     */
-    void unsubscribe(Collection<String> tradeSymbols, Collection<String> quoteSymbols, Collection<String> barSymbols);
-
-    /**
-     * Gets all of the currently subscribed control {@link MarketDataMessageType}s.
-     *
-     * @return a {@link Collection} of {@link MarketDataMessageType}s
-     */
-    Collection<MarketDataMessageType> subscribedControls();
-
-    /**
-     * Gets all of the currently subscribed symbols for trade updates.
-     *
-     * @return a {@link Collection} of {@link String}s
-     */
-    Collection<String> subscribedTrades();
-
-    /**
-     * Gets all of the currently subscribed symbols for quote updates.
-     *
-     * @return a {@link Collection} of {@link String}s
-     */
-    Collection<String> subscribedQuotes();
-
-    /**
-     * Gets all of the currently subscribed symbols for bar updates.
-     *
-     * @return a {@link Collection} of {@link String}s
-     */
-    Collection<String> subscribedBars();
-}
+public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface {}
diff --git a/schema_json/endpoint/market_data/realtime/control/error_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json
similarity index 54%
rename from schema_json/endpoint/market_data/realtime/control/error_message.json
rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json
index 6dca33c4..655d6a03 100644
--- a/schema_json/endpoint/market_data/realtime/control/error_message.json
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json
@@ -1,9 +1,8 @@
 {
   "type": "object",
   "extends": {
-    "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage"
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage"
   },
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
   "properties": {
     "code": {
       "existingJavaType": "java.lang.Integer",
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json
new file mode 100644
index 00000000..e471b400
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json
@@ -0,0 +1,13 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage"
+  },
+  "properties": {
+    "msg": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType",
+      "javaName": "messageType",
+      "title": "The success message type."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json
new file mode 100644
index 00000000..61c6c983
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json
@@ -0,0 +1,7 @@
+{
+  "type": "string",
+  "enum": [
+    "success",
+    "authenticated"
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json
new file mode 100644
index 00000000..e1a8346a
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json
@@ -0,0 +1,4 @@
+{
+  "type": "object",
+  "properties": {}
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java
new file mode 100644
index 00000000..ae8efe52
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java
@@ -0,0 +1,55 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage;
+
+/**
+ * {@link CryptoMarketDataListener} defines a listener interface for {@link CryptoMarketDataWebsocketInterface}
+ * messages.
+ */
+public interface CryptoMarketDataListener {
+
+    /**
+     * Called when a {@link CryptoTradeMessage} is received.
+     *
+     * @param trade the {@link CryptoTradeMessage}
+     */
+    void onTrade(CryptoTradeMessage trade);
+
+    /**
+     * Called when a {@link CryptoQuoteMessage} is received.
+     *
+     * @param quote the {@link CryptoQuoteMessage}
+     */
+    void onQuote(CryptoQuoteMessage quote);
+
+    /**
+     * Called when a {@link CryptoBarMessage} is received.
+     *
+     * @param bar the {@link CryptoBarMessage}
+     */
+    void onMinuteBar(CryptoBarMessage bar);
+
+    /**
+     * Called when a {@link CryptoBarMessage} is received.
+     *
+     * @param bar the {@link CryptoBarMessage}
+     */
+    void onDailyBar(CryptoBarMessage bar);
+
+    /**
+     * Called when a {@link CryptoBarMessage} is received.
+     *
+     * @param bar the {@link CryptoBarMessage}
+     */
+    void onUpdatedBar(CryptoBarMessage bar);
+
+    /**
+     * Called when a {@link CryptoOrderBookMessage} is received.
+     *
+     * @param orderBook the {@link CryptoOrderBookMessage}
+     */
+    void onOrderBook(CryptoOrderBookMessage orderBook);
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java
new file mode 100644
index 00000000..cf1cc348
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java
@@ -0,0 +1,30 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage;
+
+/**
+ * {@link CryptoMarketDataListenerAdapter} is an adapter for {@link CryptoMarketDataListener}.
+ */
+public class CryptoMarketDataListenerAdapter implements CryptoMarketDataListener {
+
+    @Override
+    public void onTrade(CryptoTradeMessage trade) {}
+
+    @Override
+    public void onQuote(CryptoQuoteMessage quote) {}
+
+    @Override
+    public void onMinuteBar(CryptoBarMessage bar) {}
+
+    @Override
+    public void onDailyBar(CryptoBarMessage bar) {}
+
+    @Override
+    public void onUpdatedBar(CryptoBarMessage bar) {}
+
+    @Override
+    public void onOrderBook(CryptoOrderBookMessage orderBook) {}
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java
new file mode 100644
index 00000000..ed3a88ad
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java
@@ -0,0 +1,175 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto;
+
+import com.google.gson.JsonObject;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.control.CryptoSubscriptionsMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage;
+import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+
+import java.util.Set;
+
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.ERROR;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUBSCRIPTION;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUCCESS;
+import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson;
+
+/**
+ * {@link CryptoMarketDataWebsocket} is an implementation for {@link CryptoMarketDataWebsocketInterface}.
+ */
+public class CryptoMarketDataWebsocket
+        extends MarketDataWebsocket<CryptoMarketDataMessageType, CryptoSubscriptionsMessage, CryptoMarketDataListener>
+        implements CryptoMarketDataWebsocketInterface {
+
+    /**
+     * Instantiates a new {@link CryptoMarketDataWebsocket}.
+     *
+     * @param okHttpClient    the {@link OkHttpClient}
+     * @param traderKeyID     the trader key ID
+     * @param traderSecretKey the trader secret key
+     * @param brokerAPIKey    the broker API key
+     * @param brokerAPISecret the broker API secret
+     */
+    public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey,
+            String brokerAPIKey, String brokerAPISecret) {
+        super(okHttpClient, new HttpUrl.Builder()
+                        .scheme("https")
+                        .host("stream.data.alpaca.markets")
+                        .addPathSegments("v1beta3/crypto/us")
+                        .build(),
+                "Crypto", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret,
+                CryptoMarketDataMessageType.class, CryptoSubscriptionsMessage.class);
+    }
+
+    @Override
+    protected boolean isSuccessMessageType(CryptoMarketDataMessageType messageType) {
+        return messageType == SUCCESS;
+    }
+
+    @Override
+    protected boolean isErrorMessageType(CryptoMarketDataMessageType messageType) {
+        return messageType == ERROR;
+    }
+
+    @Override
+    protected boolean isSubscriptionMessageType(CryptoMarketDataMessageType messageType) {
+        return messageType == SUBSCRIPTION;
+    }
+
+    @Override
+    protected void callListenerWithMessage(CryptoMarketDataMessageType messageType, JsonObject messageObject) {
+        switch (messageType) {
+            case TRADES:
+                listener.onTrade(getGson().fromJson(messageObject, CryptoTradeMessage.class));
+                break;
+            case QUOTES:
+                listener.onQuote(getGson().fromJson(messageObject, CryptoQuoteMessage.class));
+                break;
+            case MINUTE_BARS:
+                listener.onMinuteBar(getGson().fromJson(messageObject, CryptoBarMessage.class));
+                break;
+            case DAILY_BARS:
+                listener.onDailyBar(getGson().fromJson(messageObject, CryptoBarMessage.class));
+                break;
+            case UPDATED_BARS:
+                listener.onUpdatedBar(getGson().fromJson(messageObject, CryptoBarMessage.class));
+                break;
+            case ORDER_BOOKS:
+                listener.onOrderBook(getGson().fromJson(messageObject, CryptoOrderBookMessage.class));
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public void setListener(CryptoMarketDataListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void setTradeSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getTradeSubscriptions(), symbols,
+                set -> newEmptyCryptoSubscriptionsMessage().withTrades(set));
+    }
+
+    @Override
+    public Set<String> getTradeSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades();
+    }
+
+    @Override
+    public void setQuoteSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getQuoteSubscriptions(), symbols,
+                set -> newEmptyCryptoSubscriptionsMessage().withQuotes(set));
+    }
+
+    @Override
+    public Set<String> getQuoteSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes();
+    }
+
+    @Override
+    public void setMinuteBarSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getMinuteBarSubscriptions(), symbols,
+                set -> newEmptyCryptoSubscriptionsMessage().withMinuteBars(set));
+    }
+
+    @Override
+    public Set<String> getMinuteBarSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars();
+    }
+
+    @Override
+    public void setDailyBarSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getDailyBarSubscriptions(), symbols,
+                set -> newEmptyCryptoSubscriptionsMessage().withDailyBars(set));
+    }
+
+    @Override
+    public Set<String> getDailyBarSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars();
+    }
+
+    @Override
+    public void setUpdatedBarSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getUpdatedBarSubscriptions(), symbols,
+                set -> newEmptyCryptoSubscriptionsMessage().withUpdatedBars(set));
+    }
+
+    @Override
+    public Set<String> getUpdatedBarSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars();
+    }
+
+    @Override
+    public void setOrderBookSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getOrderBookSubscriptions(), symbols,
+                set -> newEmptyCryptoSubscriptionsMessage().withOrderBooks(set));
+    }
+
+    @Override
+    public Set<String> getOrderBookSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getOrderBooks();
+    }
+
+    private CryptoSubscriptionsMessage newEmptyCryptoSubscriptionsMessage() {
+        return new CryptoSubscriptionsMessage()
+                .withTrades(null)
+                .withQuotes(null)
+                .withMinuteBars(null)
+                .withDailyBars(null)
+                .withUpdatedBars(null)
+                .withOrderBooks(null);
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java
new file mode 100644
index 00000000..a24e250f
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java
@@ -0,0 +1,116 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType;
+import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface;
+
+import java.util.Set;
+
+/**
+ * {@link CryptoMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for
+ * {@link CryptoMarketDataWebsocket}.
+ */
+public interface CryptoMarketDataWebsocketInterface extends MarketDataWebsocketInterface {
+
+    /**
+     * Sets the {@link CryptoMarketDataListener}.
+     *
+     * @param listener the {@link CryptoMarketDataListener}
+     */
+    void setListener(CryptoMarketDataListener listener);
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link CryptoMarketDataMessageType#TRADES}. This will remove all
+     * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setTradeSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#TRADES}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getTradeSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link CryptoMarketDataMessageType#QUOTES}. This will remove all
+     * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setQuoteSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#QUOTES}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getQuoteSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link CryptoMarketDataMessageType#MINUTE_BARS}. This will remove
+     * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setMinuteBarSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#MINUTE_BARS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getMinuteBarSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link CryptoMarketDataMessageType#DAILY_BARS}. This will remove all
+     * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setDailyBarSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#DAILY_BARS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getDailyBarSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link CryptoMarketDataMessageType#UPDATED_BARS}. This will remove
+     * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setUpdatedBarSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#UPDATED_BARS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getUpdatedBarSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. This will remove
+     * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setOrderBookSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#ORDER_BOOKS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getOrderBookSubscriptions();
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json
new file mode 100644
index 00000000..f24e3e99
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json
@@ -0,0 +1,53 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "o": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "open",
+      "title": "The open price."
+    },
+    "h": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "high",
+      "title": "The high price."
+    },
+    "l": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "low",
+      "title": "The low price."
+    },
+    "c": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "close",
+      "title": "The close price."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp."
+    },
+    "v": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "volume",
+      "title": "The volume."
+    },
+    "n": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "tradeCount",
+      "title": "The trade count."
+    },
+    "vw": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "vwap",
+      "title": "The VWAP (Volume Weighted Average Price)."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json
new file mode 100644
index 00000000..6d8438a8
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json
@@ -0,0 +1,34 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage"
+  },
+  "properties": {
+    "trades": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to trades."
+    },
+    "quotes": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to quotes."
+    },
+    "bars": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "javaName": "minuteBars",
+      "title": "The {@link java.util.Set} of symbols subscribed to minute bars."
+    },
+    "dailyBars": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to daily bars."
+    },
+    "updatedBars": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to updated bars."
+    },
+    "orderbooks": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "javaName": "orderBooks",
+      "title": "The {@link java.util.Set} of symbols subscribed to order books."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json
new file mode 100644
index 00000000..0ced4e8f
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json
@@ -0,0 +1,13 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage"
+  },
+  "properties": {
+    "T": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType",
+      "javaName": "messageType",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType}."
+    }
+  }
+}
diff --git a/schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json
similarity index 54%
rename from schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json
rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json
index 40368ec9..a84d1728 100644
--- a/schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json
@@ -1,13 +1,15 @@
 {
   "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/\">Real-time Data</a>.",
   "enum": [
     "success",
     "error",
     "subscription",
     "t",
     "q",
-    "b"
+    "b",
+    "d",
+    "u",
+    "o"
   ],
   "javaEnums": [
     {
@@ -20,13 +22,22 @@
       "name": "SUBSCRIPTION"
     },
     {
-      "name": "TRADE"
+      "name": "TRADES"
     },
     {
-      "name": "QUOTE"
+      "name": "QUOTES"
     },
     {
-      "name": "BAR"
+      "name": "MINUTE_BARS"
+    },
+    {
+      "name": "DAILY_BARS"
+    },
+    {
+      "name": "UPDATED_BARS"
+    },
+    {
+      "name": "ORDER_BOOKS"
     }
   ]
 }
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json
new file mode 100644
index 00000000..51ce64b5
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json
@@ -0,0 +1,15 @@
+{
+  "type": "object",
+  "properties": {
+    "p": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "price",
+      "title": "The price."
+    },
+    "s": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "size",
+      "title": "The size."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json
new file mode 100644
index 00000000..79814a8a
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json
@@ -0,0 +1,33 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "a": {
+      "existingJavaType": "java.util.Set<net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry>",
+      "javaName": "asks",
+      "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} asks."
+    },
+    "b": {
+      "existingJavaType": "java.util.Set<net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry>",
+      "javaName": "bids",
+      "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} bids."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "r": {
+      "existingJavaType": "java.lang.Boolean",
+      "javaName": "reset",
+      "title": "Whether this particular {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage} contains the whole order book and not just an update. Typically <code>true</code> for the first message and <code>false</code> for subsequent messages."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json
new file mode 100644
index 00000000..d314de7a
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json
@@ -0,0 +1,38 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "ap": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "askPrice",
+      "title": "The ask price."
+    },
+    "as": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "askSize",
+      "title": "The ask size."
+    },
+    "bp": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "bidPrice",
+      "title": "The bid price."
+    },
+    "bs": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "bidSize",
+      "title": "The bid size."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json
new file mode 100644
index 00000000..13118dd7
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json
@@ -0,0 +1,38 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "i": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "tradeID",
+      "title": "The trade ID."
+    },
+    "p": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "price",
+      "title": "The trade price."
+    },
+    "s": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "size",
+      "title": "The trade size."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "tks": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeTakerSide",
+      "javaName": "takerSide",
+      "title": "The taker side."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json
new file mode 100644
index 00000000..d793fbfb
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json
@@ -0,0 +1,15 @@
+{
+  "type": "string",
+  "enum": [
+    "B",
+    "S"
+  ],
+  "javaEnums": [
+    {
+      "name": "BUY"
+    },
+    {
+      "name": "SELL"
+    }
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java
new file mode 100644
index 00000000..c47cb6dc
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java
@@ -0,0 +1,17 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.news;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage;
+
+/**
+ * {@link NewsMarketDataListener} defines a listener interface for {@link NewsMarketDataWebsocketInterface} messages.
+ */
+@FunctionalInterface
+public interface NewsMarketDataListener {
+
+    /**
+     * Called when a {@link NewsMessage} is received.
+     *
+     * @param news the {@link NewsMessage}
+     */
+    void onNews(NewsMessage news);
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java
new file mode 100644
index 00000000..2278df82
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java
@@ -0,0 +1,12 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.news;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage;
+
+/**
+ * {@link NewsMarketDataListenerAdapter} is an adapter for {@link NewsMarketDataListener}.
+ */
+public class NewsMarketDataListenerAdapter implements NewsMarketDataListener {
+
+    @Override
+    public void onNews(NewsMessage news) {}
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java
new file mode 100644
index 00000000..495f7932
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java
@@ -0,0 +1,85 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.news;
+
+import com.google.gson.JsonObject;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.control.NewsSubscriptionsMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage;
+import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+
+import java.util.Set;
+
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.ERROR;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUBSCRIPTION;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUCCESS;
+import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson;
+
+/**
+ * {@link NewsMarketDataWebsocket} is an implementation for {@link NewsMarketDataWebsocketInterface}.
+ */
+public class NewsMarketDataWebsocket
+        extends MarketDataWebsocket<NewsMarketDataMessageType, NewsSubscriptionsMessage, NewsMarketDataListener>
+        implements NewsMarketDataWebsocketInterface {
+
+    /**
+     * Instantiates a new {@link NewsMarketDataWebsocket}.
+     *
+     * @param okHttpClient    the {@link OkHttpClient}
+     * @param traderKeyID     the trader key ID
+     * @param traderSecretKey the trader secret key
+     * @param brokerAPIKey    the broker API key
+     * @param brokerAPISecret the broker API secret
+     */
+    public NewsMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey,
+            String brokerAPIKey, String brokerAPISecret) {
+        super(okHttpClient, new HttpUrl.Builder()
+                        .scheme("https")
+                        .host("stream.data.alpaca.markets")
+                        .addPathSegments("v1beta1/news")
+                        .build(),
+                "News", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret,
+                NewsMarketDataMessageType.class, NewsSubscriptionsMessage.class);
+    }
+
+    @Override
+    protected boolean isSuccessMessageType(NewsMarketDataMessageType messageType) {
+        return messageType == SUCCESS;
+    }
+
+    @Override
+    protected boolean isErrorMessageType(NewsMarketDataMessageType messageType) {
+        return messageType == ERROR;
+    }
+
+    @Override
+    protected boolean isSubscriptionMessageType(NewsMarketDataMessageType messageType) {
+        return messageType == SUBSCRIPTION;
+    }
+
+    @Override
+    protected void callListenerWithMessage(NewsMarketDataMessageType messageType, JsonObject messageObject) {
+        if (messageType == NewsMarketDataMessageType.NEWS) {
+            listener.onNews(getGson().fromJson(messageObject, NewsMessage.class));
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public void setListener(NewsMarketDataListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void setNewsSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getNewsSubscriptions(), symbols,
+                set -> new NewsSubscriptionsMessage().withNews(set));
+    }
+
+    @Override
+    public Set<String> getNewsSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getNews();
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java
new file mode 100644
index 00000000..8a89857d
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java
@@ -0,0 +1,36 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.news;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType;
+import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface;
+
+import java.util.Set;
+
+/**
+ * {@link NewsMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for
+ * {@link NewsMarketDataWebsocket}.
+ */
+public interface NewsMarketDataWebsocketInterface extends MarketDataWebsocketInterface {
+
+    /**
+     * Sets the {@link NewsMarketDataListener}.
+     *
+     * @param listener the {@link NewsMarketDataListener}
+     */
+    void setListener(NewsMarketDataListener listener);
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link NewsMarketDataMessageType#NEWS}. This will remove all
+     * previous {@link NewsMarketDataMessageType#NEWS} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setNewsSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link NewsMarketDataMessageType#NEWS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getNewsSubscriptions();
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json
new file mode 100644
index 00000000..dbd84da7
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage"
+  },
+  "properties": {
+    "news": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to news."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json
new file mode 100644
index 00000000..638a3a67
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json
@@ -0,0 +1,38 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage"
+  },
+  "properties": {
+    "id": {
+      "existingJavaType": "java.lang.Long"
+    },
+    "source": {
+      "existingJavaType": "java.lang.String"
+    },
+    "headline": {
+      "existingJavaType": "java.lang.String"
+    },
+    "author": {
+      "existingJavaType": "java.lang.String"
+    },
+    "summary": {
+      "existingJavaType": "java.lang.String"
+    },
+    "content": {
+      "existingJavaType": "java.lang.String"
+    },
+    "url": {
+      "existingJavaType": "java.lang.String"
+    },
+    "symbols": {
+      "existingJavaType": "java.util.Set<java.lang.String>"
+    },
+    "created_at": {
+      "existingJavaType": "java.time.OffsetDateTime"
+    },
+    "updated_at": {
+      "existingJavaType": "java.time.OffsetDateTime"
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json
new file mode 100644
index 00000000..1f967c5b
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json
@@ -0,0 +1,13 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage"
+  },
+  "properties": {
+    "T": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType",
+      "javaName": "messageType",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType}."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json
new file mode 100644
index 00000000..4ddd6017
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json
@@ -0,0 +1,23 @@
+{
+  "type": "string",
+  "enum": [
+    "success",
+    "error",
+    "subscription",
+    "n"
+  ],
+  "javaEnums": [
+    {
+      "name": "SUCCESS"
+    },
+    {
+      "name": "ERROR"
+    },
+    {
+      "name": "SUBSCRIPTION"
+    },
+    {
+      "name": "NEWS"
+    }
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java
new file mode 100644
index 00000000..396e6af2
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java
@@ -0,0 +1,78 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage;
+
+/**
+ * {@link StockMarketDataListener} defines a listener interface for {@link StockMarketDataWebsocketInterface} messages.
+ */
+public interface StockMarketDataListener {
+
+    /**
+     * Called when a {@link StockTradeMessage} is received.
+     *
+     * @param trade the {@link StockTradeMessage}
+     */
+    void onTrade(StockTradeMessage trade);
+
+    /**
+     * Called when a {@link StockQuoteMessage} is received.
+     *
+     * @param quote the {@link StockQuoteMessage}
+     */
+    void onQuote(StockQuoteMessage quote);
+
+    /**
+     * Called when a {@link StockBarMessage} is received.
+     *
+     * @param bar the {@link StockBarMessage}
+     */
+    void onMinuteBar(StockBarMessage bar);
+
+    /**
+     * Called when a {@link StockBarMessage} is received.
+     *
+     * @param bar the {@link StockBarMessage}
+     */
+    void onDailyBar(StockBarMessage bar);
+
+    /**
+     * Called when a {@link StockBarMessage} is received.
+     *
+     * @param bar the {@link StockBarMessage}
+     */
+    void onUpdatedBar(StockBarMessage bar);
+
+    /**
+     * Called when a {@link StockTradeCorrectionMessage} is received.
+     *
+     * @param tradeCorrection the {@link StockTradeCorrectionMessage}
+     */
+    void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection);
+
+    /**
+     * Called when a {@link StockTradeCancelErrorMessage} is received.
+     *
+     * @param tradeCancelError the {@link StockTradeCancelErrorMessage}
+     */
+    void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError);
+
+    /**
+     * Called when a {@link StockLimitUpLimitDownBandMessage} is received.
+     *
+     * @param limitUpLimitDownBand the {@link StockLimitUpLimitDownBandMessage}
+     */
+    void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand);
+
+    /**
+     * Called when a {link StockTradingStatusMessage} is received.
+     *
+     * @param tradingStatus the {@link StockTradingStatusMessage}
+     */
+    void onTradingStatus(StockTradingStatusMessage tradingStatus);
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java
new file mode 100644
index 00000000..df99badf
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java
@@ -0,0 +1,42 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage;
+
+/**
+ * {@link StockMarketDataListenerAdapter} is an adapter for {@link StockMarketDataListener}.
+ */
+public class StockMarketDataListenerAdapter implements StockMarketDataListener {
+
+    @Override
+    public void onTrade(StockTradeMessage trade) {}
+
+    @Override
+    public void onQuote(StockQuoteMessage quote) {}
+
+    @Override
+    public void onMinuteBar(StockBarMessage bar) {}
+
+    @Override
+    public void onDailyBar(StockBarMessage bar) {}
+
+    @Override
+    public void onUpdatedBar(StockBarMessage bar) {}
+
+    @Override
+    public void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection) {}
+
+    @Override
+    public void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError) {}
+
+    @Override
+    public void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand) {}
+
+    @Override
+    public void onTradingStatus(StockTradingStatusMessage tradingStatus) {}
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java
new file mode 100644
index 00000000..753d0246
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java
@@ -0,0 +1,218 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock;
+
+import com.google.gson.JsonObject;
+import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.control.StockSubscriptionsMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage;
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage;
+import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+
+import java.util.Set;
+
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.ERROR;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUBSCRIPTION;
+import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUCCESS;
+import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson;
+
+/**
+ * {@link StockMarketDataWebsocket} is an implementation for {@link StockMarketDataWebsocketInterface}.
+ */
+public class StockMarketDataWebsocket
+        extends MarketDataWebsocket<StockMarketDataMessageType, StockSubscriptionsMessage, StockMarketDataListener>
+        implements StockMarketDataWebsocketInterface {
+
+    private static HttpUrl createWebsocketURL(boolean isSandbox,
+            MarketDataWebsocketSourceType marketDataWebsocketSourceType) {
+        return new HttpUrl.Builder()
+                .scheme("https")
+                .host(isSandbox ? "stream.data.sandbox.alpaca.markets" : "stream.data.alpaca.markets")
+                .addPathSegment("v2")
+                .addPathSegment(marketDataWebsocketSourceType.toString())
+                .build();
+    }
+
+    /**
+     * Instantiates a new {@link StockMarketDataWebsocket}.
+     *
+     * @param okHttpClient                  the {@link OkHttpClient}
+     * @param traderKeyID                   the trader key ID
+     * @param traderSecretKey               the trader secret key
+     * @param brokerAPIKey                  the broker API key
+     * @param brokerAPISecret               the broker API secret
+     * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType}
+     */
+    public StockMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey,
+            String brokerAPIKey, String brokerAPISecret, MarketDataWebsocketSourceType marketDataWebsocketSourceType) {
+        super(okHttpClient,
+                createWebsocketURL(brokerAPIKey != null && brokerAPISecret != null, marketDataWebsocketSourceType),
+                "Stock", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret,
+                StockMarketDataMessageType.class, StockSubscriptionsMessage.class);
+    }
+
+    @Override
+    protected boolean isSuccessMessageType(StockMarketDataMessageType messageType) {
+        return messageType == SUCCESS;
+    }
+
+    @Override
+    protected boolean isErrorMessageType(StockMarketDataMessageType messageType) {
+        return messageType == ERROR;
+    }
+
+    @Override
+    protected boolean isSubscriptionMessageType(StockMarketDataMessageType messageType) {
+        return messageType == SUBSCRIPTION;
+    }
+
+    @Override
+    protected void callListenerWithMessage(StockMarketDataMessageType messageType, JsonObject messageObject) {
+        switch (messageType) {
+            case TRADES:
+                listener.onTrade(getGson().fromJson(messageObject,
+                        StockTradeMessage.class));
+                break;
+            case QUOTES:
+                listener.onQuote(getGson().fromJson(messageObject,
+                        StockQuoteMessage.class));
+                break;
+            case MINUTE_BARS:
+                listener.onMinuteBar(getGson().fromJson(messageObject,
+                        StockBarMessage.class));
+                break;
+            case DAILY_BARS:
+                listener.onDailyBar(getGson().fromJson(messageObject,
+                        StockBarMessage.class));
+                break;
+            case UPDATED_BARS:
+                listener.onUpdatedBar(getGson().fromJson(messageObject,
+                        StockBarMessage.class));
+                break;
+            case TRADE_CORRECTIONS:
+                listener.onTradeCorrection(getGson().fromJson(messageObject,
+                        StockTradeCorrectionMessage.class));
+                break;
+            case TRADE_CANCEL_ERRORS:
+                listener.onTradeCancelError(getGson().fromJson(messageObject,
+                        StockTradeCancelErrorMessage.class));
+                break;
+            case LIMIT_UP_LIMIT_DOWN_BANDS:
+                listener.onLimitUpLimitDownBand(getGson().fromJson(messageObject,
+                        StockLimitUpLimitDownBandMessage.class));
+                break;
+            case TRADING_STATUSES:
+                listener.onTradingStatus(getGson().fromJson(messageObject,
+                        StockTradingStatusMessage.class));
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public void setListener(StockMarketDataListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void setTradeSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getTradeSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withTrades(set));
+    }
+
+    @Override
+    public Set<String> getTradeSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades();
+    }
+
+    @Override
+    public void setQuoteSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getQuoteSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withQuotes(set));
+    }
+
+    @Override
+    public Set<String> getQuoteSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes();
+    }
+
+    @Override
+    public void setMinuteBarSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getMinuteBarSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withMinuteBars(set));
+    }
+
+    @Override
+    public Set<String> getMinuteBarSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars();
+    }
+
+    @Override
+    public void setDailyBarSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getDailyBarSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withDailyBars(set));
+    }
+
+    @Override
+    public Set<String> getDailyBarSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars();
+    }
+
+    @Override
+    public void setUpdatedBarSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getUpdatedBarSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withUpdatedBars(set));
+    }
+
+    @Override
+    public Set<String> getUpdatedBarSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars();
+    }
+
+    @Override
+    public void setLimitUpLimitDownBandSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getLimitUpLimitDownBandSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withLimitUpLimitDownBands(set));
+    }
+
+    @Override
+    public Set<String> getLimitUpLimitDownBandSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getLimitUpLimitDownBands();
+    }
+
+    @Override
+    public void setTradingStatuseSubscriptions(Set<String> symbols) {
+        symbols = symbols == null ? Set.of() : symbols;
+        setSubscriptions(getTradingStatuseSubscriptions(), symbols,
+                set -> newEmptyStockSubscriptionsMessage().withTradingStatuses(set));
+    }
+
+    @Override
+    public Set<String> getTradingStatuseSubscriptions() {
+        return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTradingStatuses();
+    }
+
+    private StockSubscriptionsMessage newEmptyStockSubscriptionsMessage() {
+        return new StockSubscriptionsMessage()
+                .withTrades(null)
+                .withQuotes(null)
+                .withMinuteBars(null)
+                .withDailyBars(null)
+                .withUpdatedBars(null)
+                .withLimitUpLimitDownBands(null)
+                .withTradingStatuses(null);
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java
new file mode 100644
index 00000000..32fc3b8d
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java
@@ -0,0 +1,136 @@
+package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock;
+
+import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType;
+import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface;
+
+import java.util.Set;
+
+/**
+ * {@link StockMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for
+ * {@link StockMarketDataWebsocket}.
+ */
+public interface StockMarketDataWebsocketInterface extends MarketDataWebsocketInterface {
+
+    /**
+     * Sets the {@link StockMarketDataListener}.
+     *
+     * @param listener the {@link StockMarketDataListener}
+     */
+    void setListener(StockMarketDataListener listener);
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#TRADES}. This will remove all
+     * previous {@link StockMarketDataMessageType#TRADES} subscriptions.
+     * <br>
+     * Note that this will automatically apply the same <code>symbols</code> subscription list to the
+     * {@link StockMarketDataMessageType#TRADE_CORRECTIONS} and {@link StockMarketDataMessageType#TRADE_CANCEL_ERRORS}
+     * types.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setTradeSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADES}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getTradeSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#QUOTES}. This will remove all
+     * previous {@link StockMarketDataMessageType#QUOTES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setQuoteSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#QUOTES}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getQuoteSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#MINUTE_BARS}. This will remove all
+     * previous {@link StockMarketDataMessageType#MINUTE_BARS} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setMinuteBarSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#MINUTE_BARS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getMinuteBarSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#DAILY_BARS}. This will remove all
+     * previous {@link StockMarketDataMessageType#DAILY_BARS} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setDailyBarSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#DAILY_BARS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getDailyBarSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#UPDATED_BARS}. This will remove
+     * all previous {@link StockMarketDataMessageType#UPDATED_BARS} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setUpdatedBarSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#UPDATED_BARS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getUpdatedBarSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. This
+     * will remove all previous {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setLimitUpLimitDownBandSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getLimitUpLimitDownBandSubscriptions();
+
+    /**
+     * Subscribes the given <code>symbols</code> to {@link StockMarketDataMessageType#TRADING_STATUSES}. This will
+     * remove all previous {@link StockMarketDataMessageType#TRADING_STATUSES} subscriptions.
+     *
+     * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available
+     *                symbols) or <code>null</code> to unsubscribe from all symbols
+     */
+    void setTradingStatuseSubscriptions(Set<String> symbols);
+
+    /**
+     * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADING_STATUSES}.
+     *
+     * @return a {@link Set} of {@link String} symbols
+     */
+    Set<String> getTradingStatuseSubscriptions();
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json
new file mode 100644
index 00000000..95c6c0d3
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json
@@ -0,0 +1,53 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "o": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "open",
+      "title": "The open price."
+    },
+    "h": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "high",
+      "title": "The high price."
+    },
+    "l": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "low",
+      "title": "The low price."
+    },
+    "c": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "close",
+      "title": "The close price."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp."
+    },
+    "v": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "volume",
+      "title": "The volume."
+    },
+    "n": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "tradeCount",
+      "title": "The trade count."
+    },
+    "vw": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "vwap",
+      "title": "The VWAP (Volume Weighted Average Price)."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json
new file mode 100644
index 00000000..05a1d449
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json
@@ -0,0 +1,39 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "trades": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to trades."
+    },
+    "quotes": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to quotes."
+    },
+    "bars": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "javaName": "minuteBars",
+      "title": "The {@link java.util.Set} of symbols subscribed to minute bars."
+    },
+    "dailyBars": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to daily bars."
+    },
+    "updatedBars": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "title": "The {@link java.util.Set} of symbols subscribed to updated bars."
+    },
+    "lulds": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "javaName": "limitUpLimitDownBands",
+      "title": "The {@link java.util.Set} of symbols subscribed to limit up - limit down bands."
+    },
+    "statuses": {
+      "existingJavaType": "java.util.Set<java.lang.String>",
+      "javaName": "tradingStatuses",
+      "title": "The {@link java.util.Set} of symbols subscribed to trading statuses."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json
new file mode 100644
index 00000000..db9238cf
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json
@@ -0,0 +1,38 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "u": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "limitUpPrice",
+      "title": "The upper limit price band of a security."
+    },
+    "d": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "limitDownPrice",
+      "title": "The lower limit price band of a security."
+    },
+    "i": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "indicator",
+      "title": "The indicator."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "z": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "tape",
+      "title": "The tape."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json
new file mode 100644
index 00000000..b0e2d72f
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json
@@ -0,0 +1,58 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "ax": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "askExchange",
+      "title": "The ask exchange code."
+    },
+    "ap": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "askPrice",
+      "title": "The ask price."
+    },
+    "as": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "askSize",
+      "title": "The ask size."
+    },
+    "bx": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "bidExchange",
+      "title": "The bid exchange code."
+    },
+    "bp": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "bidPrice",
+      "title": "The bid price."
+    },
+    "bs": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "bidSize",
+      "title": "The bid size."
+    },
+    "c": {
+      "existingJavaType": "java.util.Set<String>",
+      "javaName": "conditions",
+      "title": "The {@link java.util.Set} of quote conditions."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "z": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "tape",
+      "title": "The tape."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json
new file mode 100644
index 00000000..8fbfaf6b
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json
@@ -0,0 +1,13 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage"
+  },
+  "properties": {
+    "T": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType",
+      "javaName": "messageType",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType}."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json
new file mode 100644
index 00000000..17495738
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json
@@ -0,0 +1,55 @@
+{
+  "type": "string",
+  "enum": [
+    "success",
+    "error",
+    "subscription",
+    "t",
+    "q",
+    "b",
+    "d",
+    "u",
+    "c",
+    "x",
+    "l",
+    "s"
+  ],
+  "javaEnums": [
+    {
+      "name": "SUCCESS"
+    },
+    {
+      "name": "ERROR"
+    },
+    {
+      "name": "SUBSCRIPTION"
+    },
+    {
+      "name": "TRADES"
+    },
+    {
+      "name": "QUOTES"
+    },
+    {
+      "name": "MINUTE_BARS"
+    },
+    {
+      "name": "DAILY_BARS"
+    },
+    {
+      "name": "UPDATED_BARS"
+    },
+    {
+      "name": "TRADE_CORRECTIONS"
+    },
+    {
+      "name": "TRADE_CANCEL_ERRORS"
+    },
+    {
+      "name": "LIMIT_UP_LIMIT_DOWN_BANDS"
+    },
+    {
+      "name": "TRADING_STATUSES"
+    }
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json
new file mode 100644
index 00000000..239a8164
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json
@@ -0,0 +1,48 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "i": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "tradeID",
+      "title": "The trade ID."
+    },
+    "x": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "exchange",
+      "title": "The exchange code where the trade occurred."
+    },
+    "p": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "price",
+      "title": "The trade price."
+    },
+    "s": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "size",
+      "title": "The trade size."
+    },
+    "c": {
+      "existingJavaType": "java.util.Set<String>",
+      "javaName": "conditions",
+      "title": "The {@link java.util.Set} of trade conditions."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "z": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "tape",
+      "title": "The tape."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json
new file mode 100644
index 00000000..c13371a9
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json
@@ -0,0 +1,15 @@
+{
+  "type": "string",
+  "enum": [
+    "C",
+    "E"
+  ],
+  "javaEnums": [
+    {
+      "name": "CANCEL"
+    },
+    {
+      "name": "ERROR"
+    }
+  ]
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json
new file mode 100644
index 00000000..577586c8
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json
@@ -0,0 +1,48 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "i": {
+      "existingJavaType": "java.lang.Long",
+      "javaName": "tradeID",
+      "title": "The trade ID."
+    },
+    "x": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "exchange",
+      "title": "The exchange code where the trade cancel/error occurred."
+    },
+    "p": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "price",
+      "title": "The trade price."
+    },
+    "s": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "size",
+      "title": "The trade size."
+    },
+    "a": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorAction",
+      "javaName": "action",
+      "title": "The trade cancel/error action."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "z": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "tape",
+      "title": "The tape."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json
new file mode 100644
index 00000000..acb6a6ab
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json
@@ -0,0 +1,68 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "x": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "exchange",
+      "title": "The exchange code where the trade correction occurred."
+    },
+    "oi": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "originalTradeID",
+      "title": "The original trade ID."
+    },
+    "op": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "originalPrice",
+      "title": "The original trade price."
+    },
+    "os": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "originalSize",
+      "title": "The original trade size."
+    },
+    "oc": {
+      "existingJavaType": "java.util.Set<String>",
+      "javaName": "originalConditions",
+      "title": "The {@link java.util.Set} of original trade conditions."
+    },
+    "ci": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "correctedTradeID",
+      "title": "The corrected trade ID."
+    },
+    "cp": {
+      "existingJavaType": "java.lang.Double",
+      "javaName": "correctedPrice",
+      "title": "The corrected trade price."
+    },
+    "cs": {
+      "existingJavaType": "java.lang.Integer",
+      "javaName": "correctedSize",
+      "title": "The corrected trade size."
+    },
+    "cc": {
+      "existingJavaType": "java.util.Set<String>",
+      "javaName": "correctedConditions",
+      "title": "The {@link java.util.Set} of original trade conditions."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "z": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "tape",
+      "title": "The tape."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json
new file mode 100644
index 00000000..d1946769
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json
@@ -0,0 +1,43 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage"
+  },
+  "properties": {
+    "S": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "symbol",
+      "title": "The symbol."
+    },
+    "sc": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "statusCode",
+      "title": "The status code."
+    },
+    "sm": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "statusMessage",
+      "title": "The status message."
+    },
+    "rc": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "reasonCode",
+      "title": "The reason code."
+    },
+    "rm": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "reasonMessage",
+      "title": "The reason message."
+    },
+    "t": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "javaName": "timestamp",
+      "title": "The timestamp with nanosecond precision."
+    },
+    "z": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "tape",
+      "title": "The tape."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java
deleted file mode 100644
index 3ebf714d..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.jacobpeterson.alpaca.websocket.streaming;
-
-import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType;
-import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener;
-
-/**
- * {@link StreamingListener} defines a listener interface for {@link StreamingWebsocket} messages.
- */
-public interface StreamingListener extends AlpacaWebsocketMessageListener<StreamingMessageType, StreamingMessage> {
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java
deleted file mode 100644
index 8c52b078..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java
+++ /dev/null
@@ -1,259 +0,0 @@
-package net.jacobpeterson.alpaca.websocket.streaming;
-
-import com.google.common.collect.Iterables;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonPrimitive;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationMessage;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningMessage;
-import net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdateMessage;
-import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.WebSocket;
-import okio.ByteString;
-import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Predicates.not;
-import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON;
-
-/**
- * {@link StreamingWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link
- * StreamingWebsocketInterface} interface for
- * <a href="https://alpaca.markets/docs/api-documentation/api-v2/streaming/">Streaming</a>
- */
-public class StreamingWebsocket extends AlpacaWebsocket<StreamingMessageType, StreamingMessage, StreamingListener>
-        implements StreamingWebsocketInterface {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(StreamingWebsocket.class);
-    private static final String STREAM_ELEMENT_KEY = "stream";
-    private static final List<StreamingMessageType> SUBSCRIBABLE_STREAMING_MESSAGE_TYPES = Collections.singletonList(
-            StreamingMessageType.TRADE_UPDATES);
-
-    /**
-     * Creates a {@link HttpUrl} for {@link StreamingWebsocket} with the given <code>alpacaSubdomain</code>.
-     *
-     * @param alpacaSubdomain the Alpaca subdomain
-     *
-     * @return a {@link HttpUrl}
-     */
-    private static HttpUrl createWebsocketURL(String alpacaSubdomain) {
-        return new HttpUrl.Builder()
-                .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, but "https" works fine
-                .host(alpacaSubdomain + ".alpaca.markets")
-                .addPathSegment("stream")
-                .build();
-    }
-
-    private final Set<StreamingMessageType> listenedStreamMessageTypes;
-
-    /**
-     * Instantiates a new {@link StreamingWebsocket}.
-     *
-     * @param okHttpClient    the {@link OkHttpClient}
-     * @param alpacaSubdomain the Alpaca subdomain
-     * @param keyID           the key ID
-     * @param secretKey       the secret key
-     * @param oAuthToken      the OAuth token
-     */
-    public StreamingWebsocket(OkHttpClient okHttpClient, String alpacaSubdomain,
-            String keyID, String secretKey, String oAuthToken) {
-        super(okHttpClient, createWebsocketURL(alpacaSubdomain), "Streaming", keyID, secretKey, oAuthToken);
-
-        listenedStreamMessageTypes = new HashSet<>();
-    }
-
-    @Override
-    protected void cleanupState() {
-        super.cleanupState();
-
-        listenedStreamMessageTypes.clear();
-    }
-
-    @Override
-    protected void onConnection() {
-        sendAuthenticationMessage();
-    }
-
-    @Override
-    protected void onReconnection() {
-        sendAuthenticationMessage();
-        if (waitForAuthorization()) {
-            streams(Iterables.toArray(listenedStreamMessageTypes, StreamingMessageType.class));
-        }
-    }
-
-    @Override
-    protected void sendAuthenticationMessage() {
-        // Ensures that 'authenticationMessageFuture' exists
-        getAuthorizationFuture();
-
-        /* Authentication message format:
-         *  {
-         *      "action": "authenticate",
-         *      "data": {
-         *          "key_id": "{YOUR_API_KEY_ID}",
-         *          "secret_key": "{YOUR_API_SECRET_KEY}"
-         *      }
-         *  }
-         */
-
-        JsonObject authObject = new JsonObject();
-        authObject.addProperty("action", "authenticate");
-
-        JsonObject authData = new JsonObject();
-
-        if (useOAuth) {
-            authData.addProperty("oauth_token", oAuthToken);
-        } else {
-            authData.addProperty("key_id", keyID);
-            authData.addProperty("secret_key", secretKey);
-        }
-
-        authObject.add("data", authData);
-
-        LOGGER.info("{} websocket sending authentication message...", websocketName);
-        websocket.send(authObject.toString());
-    }
-
-    // This websocket uses binary frames and not text frames.
-    @Override
-    public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) {
-        String message = byteString.utf8();
-
-        JsonElement messageElement = JsonParser.parseString(message);
-        checkState(messageElement instanceof JsonObject, "Message must be a JsonObject! Received: %s", messageElement);
-
-        JsonObject messageObject = messageElement.getAsJsonObject();
-        JsonElement streamElement = messageObject.get(STREAM_ELEMENT_KEY);
-        checkState(streamElement instanceof JsonPrimitive,
-                "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement);
-
-        StreamingMessageType streamingMessageType = GSON.fromJson(streamElement, StreamingMessageType.class);
-        checkNotNull(streamingMessageType, "StreamingMessageType not found in message: %s", messageObject);
-
-        StreamingMessage streamingMessage;
-        switch (streamingMessageType) {
-            case AUTHORIZATION:
-                streamingMessage = GSON.fromJson(messageObject, AuthorizationMessage.class);
-
-                authenticated = isAuthorizationMessageSuccess((AuthorizationMessage) streamingMessage);
-
-                if (!authenticated) {
-                    LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, streamingMessage);
-                } else {
-                    LOGGER.info("{} websocket authenticated.", websocketName);
-                    LOGGER.debug("{}", streamingMessage);
-                }
-
-                if (authenticationMessageFuture != null) {
-                    authenticationMessageFuture.complete(authenticated);
-                }
-                break;
-            case LISTENING:
-                streamingMessage = GSON.fromJson(messageObject, ListeningMessage.class);
-                LOGGER.debug("{}", streamingMessage);
-
-                // Remove all 'StreamingMessageType's that are no longer listened to and add new ones
-                List<StreamingMessageType> currentTypes = ((ListeningMessage) streamingMessage).getData().getStreams();
-                currentTypes.stream()
-                        .filter(not(listenedStreamMessageTypes::contains))
-                        .forEach(listenedStreamMessageTypes::remove);
-                listenedStreamMessageTypes.addAll(currentTypes);
-                break;
-            case TRADE_UPDATES:
-                streamingMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class);
-                LOGGER.debug("{}", streamingMessage);
-                break;
-            default:
-                throw new UnsupportedOperationException();
-        }
-
-        if (listenedStreamMessageTypes.contains(streamingMessageType)) {
-            callListener(streamingMessageType, streamingMessage);
-        }
-    }
-
-    /**
-     * Returns true if {@link AuthorizationMessage} states success.
-     *
-     * @param authorizationMessage the {@link AuthorizationMessage}
-     *
-     * @return a boolean
-     */
-    private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) {
-        AuthorizationData authorizationData = authorizationMessage.getData();
-        return authorizationData.getStatus().equalsIgnoreCase("authorized") &&
-                authorizationData.getAction().equalsIgnoreCase("authenticate");
-    }
-
-    @Override
-    public void streams(StreamingMessageType... streamingMessageTypes) {
-        if (streamingMessageTypes == null || streamingMessageTypes.length == 0) {
-            return;
-        }
-
-        // Add all non-subscribable 'streamingMessageTypes' before connecting or sending websocket message
-        Arrays.stream(streamingMessageTypes)
-                .filter(not(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains))
-                .forEach(listenedStreamMessageTypes::add);
-
-        if (!isConnected()) {
-            connect();
-
-            if (!waitForAuthorization()) {
-                LOGGER.error("Not subscribing to streams due to unauthorized {} websocket.", websocketName);
-                return;
-            }
-        }
-
-        // Stream request format:
-        // {
-        //     "action": "listen",
-        //     "data": {
-        //         "streams": ["trade_updates"]
-        //     }
-        // }
-
-        JsonObject requestObject = new JsonObject();
-        requestObject.addProperty("action", "listen");
-
-        JsonArray streamsArray = new JsonArray();
-        Arrays.stream(streamingMessageTypes)
-                .filter(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains)
-                .forEach((type) -> streamsArray.add(type.toString()));
-
-        if (streamsArray.isEmpty()) {
-            return;
-        }
-
-        JsonObject dataObject = new JsonObject();
-        dataObject.add("streams", streamsArray);
-
-        requestObject.add("data", dataObject);
-
-        websocket.send(requestObject.toString());
-        LOGGER.info("Requested streams: {}.", streamsArray);
-    }
-
-    @Override
-    public Collection<StreamingMessageType> streams() {
-        return new HashSet<>(listenedStreamMessageTypes);
-    }
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java
deleted file mode 100644
index 9b2dc5ca..00000000
--- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package net.jacobpeterson.alpaca.websocket.streaming;
-
-import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType;
-import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface;
-
-import java.util.Collection;
-
-/**
- * {@link StreamingWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link StreamingWebsocket}.
- */
-public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface<StreamingListener> {
-
-    /**
-     * Sets the {@link StreamingMessageType}s to <code>streamingMessageTypes</code>.
-     * <br>
-     * Note that this will call {@link StreamingWebsocketInterface#connect()} and {@link
-     * StreamingWebsocketInterface#waitForAuthorization()} if {@link StreamingWebsocketInterface#isConnected()} returns
-     * false.
-     *
-     * @param streamingMessageTypes the {@link StreamingMessageType}s
-     */
-    void streams(StreamingMessageType... streamingMessageTypes);
-
-    /**
-     * Gets the {@link StreamingMessageType}s.
-     *
-     * @return a {@link Collection} of {@link StreamingMessageType}
-     */
-    Collection<StreamingMessageType> streams();
-}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java
new file mode 100644
index 00000000..3a752308
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java
@@ -0,0 +1,17 @@
+package net.jacobpeterson.alpaca.websocket.updates;
+
+import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage;
+
+/**
+ * {@link UpdatesListener} defines a listener interface for {@link UpdatesWebsocketInterface} messages.
+ */
+@FunctionalInterface
+public interface UpdatesListener {
+
+    /**
+     * Called when a {@link TradeUpdateMessage} is received.
+     *
+     * @param tradeUpdate the {@link TradeUpdateMessage}
+     */
+    void onTradeUpdate(TradeUpdateMessage tradeUpdate);
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java
new file mode 100644
index 00000000..ae31a623
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java
@@ -0,0 +1,169 @@
+package net.jacobpeterson.alpaca.websocket.updates;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType;
+import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType;
+import net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationMessage;
+import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage;
+import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.WebSocket;
+import okio.ByteString;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.gson.JsonParser.parseString;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType.TRADE_UPDATES;
+import static net.jacobpeterson.alpaca.openapi.trader.JSON.getGson;
+
+/**
+ * {@link UpdatesWebsocket} is an {@link AlpacaWebsocket} implementation and provides the
+ * {@link UpdatesWebsocketInterface} interface for
+ * <a href="https://docs.alpaca.markets/docs/websocket-streaming">Updates Streaming</a>.
+ */
+public class UpdatesWebsocket extends AlpacaWebsocket implements UpdatesWebsocketInterface {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(UpdatesWebsocket.class);
+
+    @SuppressWarnings("UnnecessaryDefault")
+    private static HttpUrl createWebsocketURL(TraderAPIEndpointType traderAPIEndpointType) {
+        return new HttpUrl.Builder()
+                .scheme("https")
+                .host((switch (traderAPIEndpointType) {
+                    case LIVE -> "api";
+                    case PAPER -> "paper-api";
+                    default -> throw new UnsupportedOperationException();
+                }) + ".alpaca.markets")
+                .addPathSegment("stream")
+                .build();
+    }
+
+    protected final String keyID;
+    protected final String secretKey;
+    protected final String oAuthToken;
+    protected UpdatesListener listener;
+    protected boolean listenToTradeUpdates;
+
+    /**
+     * Instantiates a new {@link UpdatesWebsocket}.
+     *
+     * @param okHttpClient          the {@link OkHttpClient}
+     * @param traderAPIEndpointType the {@link TraderAPIEndpointType}
+     * @param keyID                 the key ID
+     * @param secretKey             the secret key
+     * @param oAuthToken            the OAuth token
+     */
+    public UpdatesWebsocket(OkHttpClient okHttpClient, TraderAPIEndpointType traderAPIEndpointType,
+            String keyID, String secretKey, String oAuthToken) {
+        super(okHttpClient, createWebsocketURL(traderAPIEndpointType), "Trades Stream");
+        this.keyID = keyID;
+        this.secretKey = secretKey;
+        this.oAuthToken = oAuthToken;
+    }
+
+    @Override
+    protected void cleanupState() {
+        super.cleanupState();
+    }
+
+    @Override
+    protected void onConnection() {
+        sendAuthenticationMessage();
+    }
+
+    @Override
+    protected void onReconnection() {
+        sendAuthenticationMessage();
+        if (waitForAuthorization(5, SECONDS) && listenToTradeUpdates) {
+            sendTradeUpdatesListenMessage();
+        }
+    }
+
+    @Override
+    protected void sendAuthenticationMessage() {
+        // Ensure that the authorization Future exists
+        getAuthorizationFuture();
+
+        final JsonObject authObject = new JsonObject();
+        authObject.addProperty("action", "authenticate");
+        final JsonObject authData = new JsonObject();
+        if (oAuthToken != null) {
+            authData.addProperty("oauth_token", oAuthToken);
+        } else {
+            authData.addProperty("key_id", keyID);
+            authData.addProperty("secret_key", secretKey);
+        }
+        authObject.add("data", authData);
+
+        LOGGER.info("{} websocket sending authentication message...", websocketName);
+        sendWebsocketMessage(authObject.toString());
+    }
+
+    @Override
+    public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { // Binary framing
+        final String messageString = byteString.utf8();
+        LOGGER.trace("Websocket message received: message={}", messageString);
+
+        // Parse JSON string and identify 'messageType'
+        final JsonObject messageObject = parseString(messageString).getAsJsonObject();
+        final UpdatesMessageType messageType =
+                getGson().fromJson(messageObject.get("stream"), UpdatesMessageType.class);
+
+        // Deserialize message based on 'messageType' and call listener
+        switch (messageType) {
+            case AUTHORIZATION:
+                final AuthorizationMessage authorizationMessage =
+                        getGson().fromJson(messageObject, AuthorizationMessage.class);
+                authenticated = authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate") &&
+                        authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized");
+                if (authenticationMessageFuture != null) {
+                    authenticationMessageFuture.complete(authenticated);
+                }
+                if (!authenticated) {
+                    throw new RuntimeException(websocketName + " websocket authentication failed!");
+                } else {
+                    LOGGER.info("{} websocket authenticated.", websocketName);
+                }
+                break;
+            case LISTENING:
+                break;
+            case TRADE_UPDATES:
+                if (listener != null) {
+                    listener.onTradeUpdate(getGson().fromJson(messageObject, TradeUpdateMessage.class));
+                }
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public void setListener(UpdatesListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void subscribeToTradeUpdates(boolean subscribe) {
+        listenToTradeUpdates = subscribe;
+        sendTradeUpdatesListenMessage();
+    }
+
+    private void sendTradeUpdatesListenMessage() {
+        final JsonObject requestObject = new JsonObject();
+        requestObject.addProperty("action", "listen");
+        final JsonArray streamsArray = new JsonArray();
+        if (listenToTradeUpdates) {
+            streamsArray.add(TRADE_UPDATES.toString());
+        }
+        final JsonObject dataObject = new JsonObject();
+        dataObject.add("streams", streamsArray);
+        requestObject.add("data", dataObject);
+
+        sendWebsocketMessage(requestObject.toString());
+        LOGGER.info("Requested streams: streams={}.", streamsArray);
+    }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java
new file mode 100644
index 00000000..0c86554c
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java
@@ -0,0 +1,24 @@
+package net.jacobpeterson.alpaca.websocket.updates;
+
+import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType;
+import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface;
+
+/**
+ * {@link UpdatesWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link UpdatesWebsocket}.
+ */
+public interface UpdatesWebsocketInterface extends AlpacaWebsocketInterface {
+
+    /**
+     * Sets the {@link UpdatesListener}.
+     *
+     * @param listener the {@link UpdatesListener}
+     */
+    void setListener(UpdatesListener listener);
+
+    /**
+     * Subscribes to {@link UpdatesMessageType#TRADE_UPDATES}.
+     *
+     * @param subscribe <code>true</code> to subscribe, <code>false</code> otherwise
+     */
+    void subscribeToTradeUpdates(boolean subscribe);
+}
diff --git a/schema_json/endpoint/streaming/authorization/authorization_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json
similarity index 69%
rename from schema_json/endpoint/streaming/authorization/authorization_data.json
rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json
index e88df4d5..f05ae9bd 100644
--- a/schema_json/endpoint/streaming/authorization/authorization_data.json
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json
@@ -1,6 +1,5 @@
 {
   "type": "object",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/streaming/\">Streaming</a>.",
   "properties": {
     "status": {
       "existingJavaType": "java.lang.String",
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json
new file mode 100644
index 00000000..4c991d10
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage"
+  },
+  "properties": {
+    "data": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData}."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json
new file mode 100644
index 00000000..b2798264
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "streams": {
+      "existingJavaType": "java.util.Set<net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType>",
+      "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}s."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json
new file mode 100644
index 00000000..0590794f
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage"
+  },
+  "properties": {
+    "data": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData}."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json
new file mode 100644
index 00000000..27f86dc2
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json
@@ -0,0 +1,35 @@
+{
+  "type": "object",
+  "properties": {
+    "event": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent}."
+    },
+    "execution_id": {
+      "existingJavaType": "java.lang.String",
+      "title": "The execution ID."
+    },
+    "price": {
+      "existingJavaType": "java.lang.String",
+      "title": "The price."
+    },
+    "position_qty": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "positionQuantity",
+      "title": "The position quantity."
+    },
+    "qty": {
+      "existingJavaType": "java.lang.String",
+      "javaName": "quantity",
+      "title": "The quantity."
+    },
+    "timestamp": {
+      "existingJavaType": "java.time.OffsetDateTime",
+      "title": "The timestamp."
+    },
+    "order": {
+      "existingJavaType": "net.jacobpeterson.alpaca.openapi.trader.model.Order",
+      "title": "The {@link net.jacobpeterson.alpaca.openapi.trader.model.Order}."
+    }
+  }
+}
diff --git a/schema_json/endpoint/streaming/trade/enums/trade_update_event.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json
similarity index 83%
rename from schema_json/endpoint/streaming/trade/enums/trade_update_event.json
rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json
index e422fe52..1277728e 100644
--- a/schema_json/endpoint/streaming/trade/enums/trade_update_event.json
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json
@@ -1,6 +1,5 @@
 {
   "type": "string",
-  "title": "See <a href=\"https://alpaca.markets/docs/api-documentation/api-v2/orders/\">Orders</a>.",
   "enum": [
     "new",
     "fill",
@@ -24,13 +23,13 @@
       "description": "Sent when an order has been routed to exchanges for execution."
     },
     {
-      "description": "Sent when your order has been completely filled."
+      "description": "Sent when an order has been completely filled."
     },
     {
       "description": "Sent when a number of shares less than the total remaining quantity on your order has been filled."
     },
     {
-      "description": "Sent when your requested cancelation of an order is processed."
+      "description": "Sent when your requested cancellation of an order is processed."
     },
     {
       "description": "Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value."
@@ -51,7 +50,7 @@
       "description": "Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred."
     },
     {
-      "description": "Sent when the order is awaiting cancelation. Most cancelations will occur without the order entering this state."
+      "description": "Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state."
     },
     {
       "description": "Sent when the order is awaiting replacement."
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json
new file mode 100644
index 00000000..af22fa58
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "extends": {
+    "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage"
+  },
+  "properties": {
+    "data": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate}."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json
new file mode 100644
index 00000000..ad625676
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "stream": {
+      "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType",
+      "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}."
+    }
+  }
+}
diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json
new file mode 100644
index 00000000..98ab6ea6
--- /dev/null
+++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json
@@ -0,0 +1,8 @@
+{
+  "type": "string",
+  "enum": [
+    "listening",
+    "authorization",
+    "trade_updates"
+  ]
+}
diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties
deleted file mode 100644
index 0cef7e43..00000000
--- a/src/main/resources/alpaca.default.properties
+++ /dev/null
@@ -1,11 +0,0 @@
-#Example:
-#key_id = <your Key ID>
-#secret_key = <your Secret Key>
-#endpoint_api_type = <must be either "paper" or "live">
-#data_api_type = <must be either "iex" or "sip">
-#user_agent = <some User Agent string>
-
-#Defaults:
-endpoint_api_type=paper
-data_api_type=iex
-user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1
diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java
deleted file mode 100644
index 00fe9e96..00000000
--- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java
+++ /dev/null
@@ -1,281 +0,0 @@
-package net.jacobpeterson.alpaca.test.live;
-
-import net.jacobpeterson.alpaca.model.endpoint.account.Account;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity;
-import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType;
-import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration;
-import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck;
-import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail;
-import net.jacobpeterson.alpaca.model.endpoint.clock.Clock;
-import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection;
-import net.jacobpeterson.alpaca.model.endpoint.order.Order;
-import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus;
-import net.jacobpeterson.alpaca.AlpacaAPI;
-import net.jacobpeterson.alpaca.rest.AlpacaClientException;
-import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint;
-import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint;
-import org.junit.jupiter.api.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.ZonedDateTime;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * {@link AlpacaAPITest} tests live endpoints using Alpaca Paper credentials given in the
- * <code>alpaca.properties</code> file on the classpath.
- */
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-public class AlpacaAPITest {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPITest.class);
-    private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting
-
-    static {
-        // Log trace-level
-        System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace");
-    }
-
-    private static AlpacaAPI alpacaAPI;
-    private static boolean marketOpen;
-    private static AccountConfiguration accountConfiguration;
-
-    /**
-     * Executed before all tests in this class.
-     */
-    @BeforeAll
-    public static void beforeAll() {
-        alpacaAPI = new AlpacaAPI();
-        marketOpen = false;
-    }
-
-    /**
-     * Executed before each test.
-     */
-    @BeforeEach
-    public void beforeEach() {
-    }
-
-    /**
-     * Executed after each test. Note that this will {@link Thread#sleep(long)} for {@link #RATE_LIMIT_MILLIS} to
-     * protect against rate limiting.
-     */
-    @AfterEach
-    public void afterEach() {
-        try {
-            Thread.sleep(RATE_LIMIT_MILLIS);
-        } catch (InterruptedException exception) {
-            fail("Interrupted when tearing down!");
-        }
-    }
-
-    /**
-     * Tests {@link ClockEndpoint#get()}.
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    @Test
-    @org.junit.jupiter.api.Order(1)
-    public void test_ClockEndpoint_get() throws AlpacaClientException {
-        Clock clock = alpacaAPI.clock().get();
-        assertNotNull(clock);
-
-        LOGGER.debug("{}", clock);
-
-        assertNotNull(clock.getTimestamp());
-        assertNotNull(clock.getIsOpen());
-        assertNotNull(clock.getNextOpen());
-        assertNotNull(clock.getNextClose());
-
-        marketOpen = clock.getIsOpen();
-        if (marketOpen) {
-            LOGGER.info("Market is currently open! All live endpoints will be tested.");
-        } else {
-            LOGGER.info("Market is currently closed! Only some live endpoints will be tested.");
-        }
-    }
-
-    /**
-     * Tests {@link AccountEndpoint#get()}.
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     * @throws NumberFormatException thrown for {@link NumberFormatException}s
-     */
-    @Test
-    @SuppressWarnings("ResultOfMethodCallIgnored")
-    public void test_AccountEndpoint_get() throws AlpacaClientException, NumberFormatException {
-        Account account = alpacaAPI.account().get();
-        assertNotNull(account);
-
-        LOGGER.debug("{}", account);
-
-        // Assert basic data integrity and not null
-        Double.parseDouble(account.getCash());
-        Double.parseDouble(account.getPortfolioValue());
-        Double.parseDouble(account.getLongMarketValue());
-        Double.parseDouble(account.getShortMarketValue());
-        Double.parseDouble(account.getEquity());
-        Double.parseDouble(account.getLastEquity());
-        Double.parseDouble(account.getBuyingPower());
-        Double.parseDouble(account.getInitialMargin());
-        Double.parseDouble(account.getMaintenanceMargin());
-        Double.parseDouble(account.getLastMaintenanceMargin());
-        Double.parseDouble(account.getDaytradingBuyingPower());
-        Double.parseDouble(account.getRegtBuyingPower());
-
-        // Assert other data exists
-        assertNotNull(account.getId());
-        assertNotNull(account.getAccountNumber());
-        assertNotNull(account.getStatus());
-        assertNotNull(account.getCurrency());
-        assertNotNull(account.getPatternDayTrader());
-        assertNotNull(account.getTradeSuspendedByUser());
-        assertNotNull(account.getTradingBlocked());
-        assertNotNull(account.getTransfersBlocked());
-        assertNotNull(account.getAccountBlocked());
-        assertNotNull(account.getCreatedAt());
-        assertNotNull(account.getShortingEnabled());
-        assertNotNull(account.getMultiplier());
-        assertNotNull(account.getSma());
-        assertNotNull(account.getDaytradeCount());
-    }
-
-    /**
-     * Tests @{@link AccountActivitiesEndpoint#get(ZonedDateTime,
-     * ZonedDateTime, ZonedDateTime, SortDirection, Integer, String, ActivityType...)} one {@link AccountActivity}
-     * exists until now.
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    @Test
-    public void test_AccountActivitiesEndpoint_get_One_Activity_Exists_Until_Now() throws AlpacaClientException {
-        List<AccountActivity> accountActivities = alpacaAPI.accountActivities().get(
-                null,
-                ZonedDateTime.now(),
-                null,
-                SortDirection.ASCENDING,
-                1,
-                null,
-                (ActivityType[]) null);
-        assertNotNull(accountActivities);
-        assertFalse(accountActivities.isEmpty());
-
-        accountActivities.forEach(accountActivity -> LOGGER.debug(accountActivity.toString()));
-
-        AccountActivity accountActivity = accountActivities.get(0);
-        if (accountActivity instanceof TradeActivity) {
-            TradeActivity tradeActivity = (TradeActivity) accountActivity;
-            assertNotNull(tradeActivity.getActivityType());
-            assertNotNull(tradeActivity.getId());
-            assertNotNull(tradeActivity.getCumQty());
-            assertNotNull(tradeActivity.getLeavesQty());
-            assertNotNull(tradeActivity.getPrice());
-            assertNotNull(tradeActivity.getQty());
-            assertNotNull(tradeActivity.getSide());
-            assertNotNull(tradeActivity.getSymbol());
-            assertNotNull(tradeActivity.getTransactionTime());
-            assertNotNull(tradeActivity.getOrderId());
-            assertNotNull(tradeActivity.getType());
-        } else if (accountActivity instanceof NonTradeActivity) {
-            NonTradeActivity nonTradeActivity = (NonTradeActivity) accountActivity;
-            assertNotNull(nonTradeActivity.getActivityType());
-            assertNotNull(nonTradeActivity.getId());
-            assertNotNull(nonTradeActivity.getDate());
-            assertNotNull(nonTradeActivity.getNetAmount());
-            assertNotNull(nonTradeActivity.getSymbol());
-            assertNotNull(nonTradeActivity.getQty());
-            assertNotNull(nonTradeActivity.getPerShareAmount());
-            assertNotNull(nonTradeActivity.getDescription());
-        }
-    }
-
-    /**
-     * Test {@link AccountConfigurationEndpoint#get()}.
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    @Test
-    @org.junit.jupiter.api.Order(1)
-    public void test_AccountConfigurationEndpoint_get() throws AlpacaClientException {
-        AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get();
-        assertNotNull(accountConfiguration);
-
-        LOGGER.debug("{}", accountConfiguration);
-
-        assertNotNull(accountConfiguration.getDtbpCheck());
-        assertNotNull(accountConfiguration.getTradeConfirmEmail());
-        assertNotNull(accountConfiguration.getSuspendTrade());
-        assertNotNull(accountConfiguration.getNoShorting());
-
-        AlpacaAPITest.accountConfiguration = accountConfiguration;
-    }
-
-    /**
-     * Test {@link AccountConfigurationEndpoint#set(AccountConfiguration)}.
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    @Test
-    @org.junit.jupiter.api.Order(2)
-    public void test_AccountConfigurationEndpoint_set() throws AlpacaClientException {
-        if (accountConfiguration == null) {
-            AccountConfiguration newAccountConfiguration = new AccountConfiguration(
-                    DTBPCheck.BOTH,
-                    TradeConfirmEmail.NONE,
-                    false,
-                    false);
-            LOGGER.info("Settings Account Configuration to: {}", newAccountConfiguration);
-            alpacaAPI.accountConfiguration().set(newAccountConfiguration);
-        } else {
-            alpacaAPI.accountConfiguration().set(accountConfiguration);
-        }
-    }
-
-    /**
-     * Test {@link OrdersEndpoint#get(CurrentOrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean,
-     * List)} one {@link Order} exists until now.
-     *
-     * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
-     */
-    @Test
-    public void test_OrdersEndpoint_get_One_Order_Exists_Until_now() throws AlpacaClientException {
-        List<Order> orders = alpacaAPI.orders().get(
-                CurrentOrderStatus.ALL,
-                1,
-                null,
-                ZonedDateTime.now(),
-                SortDirection.ASCENDING,
-                true,
-                null);
-
-        assertNotNull(orders);
-        assertFalse(orders.isEmpty());
-
-        orders.forEach(order -> LOGGER.debug("{}", order));
-
-        // Assert required fields are present
-        Order order = orders.get(0);
-        assertNotNull(order.getId());
-        assertNotNull(order.getClientOrderId());
-        assertNotNull(order.getCreatedAt());
-        assertNotNull(order.getUpdatedAt());
-        assertNotNull(order.getSubmittedAt());
-        assertNotNull(order.getAssetId());
-        assertNotNull(order.getSymbol());
-        assertNotNull(order.getAssetClass());
-        assertNotNull(order.getQty());
-        assertNotNull(order.getFilledQty());
-        assertNotNull(order.getType());
-        assertNotNull(order.getSide());
-        assertNotNull(order.getTimeInForce());
-        assertNotNull(order.getStatus());
-        assertNotNull(order.getExtendedHours());
-    }
-}
diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java
deleted file mode 100644
index d5f4f320..00000000
--- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package net.jacobpeterson.alpaca.test.mock;
-
-import net.jacobpeterson.alpaca.AlpacaAPI;
-import net.jacobpeterson.alpaca.model.properties.DataAPIType;
-import net.jacobpeterson.alpaca.model.properties.EndpointAPIType;
-import okhttp3.OkHttpClient;
-import org.junit.jupiter.api.Test;
-
-/**
- * {@link AlpacaAPITest} tests {@link AlpacaAPI} using mocked objects with Mockito.
- */
-public class AlpacaAPITest {
-
-    static {
-        System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson.alpaca", "trace");
-    }
-
-    /**
-     * Tests {@link AlpacaAPI#AlpacaAPI()}.
-     */
-    @Test
-    public void testAlpacaAPIConstructor_Default() {
-        new AlpacaAPI();
-    }
-
-    /**
-     * Tests {@link AlpacaAPI#AlpacaAPI(String, String)}.
-     */
-    @Test
-    public void testAlpacaAPIConstructor_keyID_secret() {
-        String keyID = "ABCDEFGHIJKLM";
-        String secret = "NOPQURSTUVWXYZ";
-
-        new AlpacaAPI(keyID, secret);
-    }
-
-    /**
-     * Tests {@link AlpacaAPI#AlpacaAPI(String, String, EndpointAPIType, DataAPIType)}.
-     */
-    @Test
-    public void testAlpacaAPIConstructor_keyID_secret_endpointAPIType_dataAPIType() {
-        String keyID = "ABCDEFGHIJKLM";
-        String secret = "NOPQURSTUVWXYZ";
-
-        new AlpacaAPI(keyID, secret, EndpointAPIType.PAPER, DataAPIType.IEX);
-        new AlpacaAPI(keyID, secret, EndpointAPIType.LIVE, DataAPIType.SIP);
-    }
-
-    /**
-     * Tests {@link AlpacaAPI#AlpacaAPI(String)}.
-     */
-    @Test
-    public void testAlpacaAPIConstructor_oAuthToken() {
-        String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ";
-
-        new AlpacaAPI(oAuthToken);
-    }
-
-    /**
-     * Tests {@link AlpacaAPI#AlpacaAPI(OkHttpClient, String, String, String, EndpointAPIType, DataAPIType)}.
-     */
-    @Test
-    public void testAlpacaAPIConstructor_okHttpClient_keyID_secret_oAuthToken_endpointAPIType_dataAPIType() {
-        OkHttpClient okHttpClient = new OkHttpClient();
-        String keyID = "ABCDEFGHIJKLM";
-        String secret = "NOPQURSTUVWXYZ";
-        String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ";
-
-        new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX);
-        new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP);
-        new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX);
-        new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP);
-    }
-}
diff --git a/src/test/resources/alpaca.default.properties b/src/test/resources/alpaca.default.properties
deleted file mode 100644
index 58a3702f..00000000
--- a/src/test/resources/alpaca.default.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-key_id=ABCDEFGHIJKLM
-secret_key=NOPQURSTUVWXYZ
-endpoint_api_type=paper
-data_api_type=iex
-user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1
