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 @@
-

+
-
-
-
-
-
+
+
+
+
# Overview
-This is a Java implementation for the Alpaca 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:
```
- net.jacobpeterson
+ net.jacobpeterson.alpaca
alpaca-java
- 8.1
- compile
+ 10.0.1
```
-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 =
-secret_key =
-endpoint_api_type =
-data_api_type =
-```
-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 = "";
-String secretKey = "";
-AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secret);
-
-// This constructor is for OAuth tokens
-String oAuthToken = "";
-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 = "";
+final String secretKey = "";
+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 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 = "";
+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 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 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 = "";
+final String brokerAPISecret = "";
+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 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 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 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(""),
+ 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 = 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 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 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 jsonschema2pojo with special configurations
+ * and generation logic.
+ * @see GenerateJsonSchemaJavaTask
+ */
+class GeneratePOJOsTask extends DefaultTask {
+
+ @ReplacedBy("configurationString")
+ GenerationConfig configuration
+
+ @Input
+ String getConfigurationString() {
+ configuration.toString()
+ }
+
+ @Internal
+ List 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 jsonschema2pojo Gradle plugin
+ * to add/change some features.
+ */
+class JSONSchema2POJOAdaptedPlugin implements Plugin {
+
+ 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 Account.",
- "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 Account.",
- "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 Account Activities.",
- "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 Account Activities.",
- "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 Account Activities.",
- "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 Account Activities.",
- "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 Account Activities.",
- "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 Account Activities.",
- "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 Account Configuration.",
- "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 Account Configuration.",
- "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 Account Configuration.",
- "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 Assets.",
- "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 Assets.",
- "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 Calendar.",
- "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 Clock.",
- "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 Historical Data.",
- "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 Historical Data.",
- "properties": {
- "bars": {
- "existingJavaType": "java.util.ArrayList",
- "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 Historical Data.",
- "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 Historical Data.",
- "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 Historical Data.",
- "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",
- "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 Historical Data.",
- "properties": {
- "quotes": {
- "existingJavaType": "java.util.ArrayList",
- "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 Historical Data.",
- "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 Historical Data.",
- "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 Historical Data.",
- "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",
- "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 Historical Data.",
- "properties": {
- "trades": {
- "existingJavaType": "java.util.ArrayList",
- "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 Real-time Data.",
- "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 Real-time Data.",
- "properties": {
- "trades": {
- "existingJavaType": "java.util.ArrayList",
- "title": "The {@link java.util.ArrayList} of subscribed trades."
- },
- "quotes": {
- "existingJavaType": "java.util.ArrayList",
- "title": "The {@link java.util.ArrayList} of subscribed quotes."
- },
- "bars": {
- "existingJavaType": "java.util.ArrayList",
- "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 Real-time Data.",
- "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 Real-time Data.",
- "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 Real-time Data.",
- "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",
- "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 Real-time Data.",
- "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 Real-time Data.",
- "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",
- "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 Orders.",
- "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 Orders.",
- "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 Orders.",
- "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 Orders.",
- "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 Orders.",
- "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 Orders.",
- "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 Orders.",
- "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 < $50 (or 2.5% higher than a stop price >= $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 Orders.",
- "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",
- "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 Portfolio History.",
- "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 Portfolio History.",
- "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 Portfolio History.",
- "properties": {
- "data_points": {
- "existingJavaType": "java.util.ArrayList",
- "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 Portfolio History.",
- "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 Portfolio History.",
- "properties": {
- "timestamp": {
- "existingJavaType": "java.util.ArrayList",
- "title": "Time of each data element, left-labeled (the beginning of time window)."
- },
- "equity": {
- "existingJavaType": "java.util.ArrayList",
- "title": "Equity value of the account in dollar amount as of the end of each time window."
- },
- "profit_loss": {
- "existingJavaType": "java.util.ArrayList",
- "title": "Profit/loss in dollar from the base value."
- },
- "profit_loss_pct": {
- "existingJavaType": "java.util.ArrayList",
- "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 https://alpaca.markets/docs/api-documentation/api-v2/positions/",
- "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 Positions.",
- "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 Streaming.",
- "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 Streaming.",
- "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 Streaming.",
- "properties": {
- "streams": {
- "existingJavaType": "java.util.ArrayList",
- "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 Streaming.",
- "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 Streaming.",
- "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 Streaming.",
- "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 Streaming.",
- "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 Watchlist.",
- "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",
- "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 null 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 Alpaca API Documentation
+ * @see Alpaca Docs
*/
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 alpaca.properties 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 null 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 null 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 null 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 null 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 null 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 200.
- */
- public 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 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 requestObject(Request request, Predicate isSuccessCode, Type type)
- throws AlpacaClientException {
- return GSON.fromJson(requestJSON(request, isSuccessCode), type);
- }
-
- /**
- * Calls {@link #requestJSON(Request, Predicate)} with a success code of 200.
- */
- 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 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 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}. Be sure to call {@link Response#close()} after use.
- *
- * @throws AlpacaClientException thrown for {@link AlpacaClientException}s
- */
- public Response request(Request request, Predicate 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}. Be sure to call {@link Response#close()} after use.
- *
- * @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:
- *
- * {
- * "code": 505,
- * "message": "Error message"
- * }
- *
- * 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 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 null 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 if this websocket is connected, false otherwise.
*
* @return a boolean
*/
boolean isConnected();
/**
- * Returns true if this websocket is authenticated.
+ * Returns true if this websocket is authenticated, false otherwise.
*
* @return a boolean
*/
boolean isAuthenticated();
+ /**
+ * Returns true if {@link #isConnected()} and {@link #isAuthenticated()}, false
+ * 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.
+ *
+ * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely
+ * never complete.
*
* @return a {@link Boolean} {@link Future}
*/
Future getAuthorizationFuture();
/**
- * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. This will timeout after 10 seconds
- * and then return false.
+ * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when timeout
+ * time has elapsed, then this will return false.
+ *
+ * @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 true if this websocket automatically reconnects, false 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. true by default.
*
- * @param listener the {@link AlpacaWebsocketMessageListener}
+ * @param automaticallyReconnect true to automatically reconnect, false 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 the 'message type' type parameter
- * @param the 'message' type parameter
- */
-@FunctionalInterface
-public interface AlpacaWebsocketMessageListener {
-
- /**
- * 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 {
-}
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
- * Real-time
- * Market Data
+ * {@link MarketDataWebsocket} is an abstract {@link AlpacaWebsocket} implementation for
+ * Market Data Streaming.
+ *
+ * @param the 'message type' type
+ * @param the 'subscription message' type
+ * @param the 'listener' type
*/
-public class MarketDataWebsocket extends AlpacaWebsocket
+public abstract class MarketDataWebsocket 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 AUTH_FAILURE_MESSAGES = Arrays.asList(
- "auth failed",
- "auth timeout",
- "not authenticated");
- private static final List SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES = Arrays.asList(
- MarketDataMessageType.TRADE,
- MarketDataMessageType.QUOTE,
- MarketDataMessageType.BAR);
+ private static final Set AUTH_FAILURE_MESSAGES = Set.of("auth failed", "auth timeout", "not authenticated");
- /**
- * Creates a {@link HttpUrl} for {@link MarketDataWebsocket} with the given dataAPIType.
- *
- * @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 listenedMarketDataMessageTypes;
- private final Set subscribedTrades;
- private final Set subscribedQuotes;
- private final Set subscribedBars;
+ protected final String authKey;
+ protected final String authSecret;
+ protected final Class messageTypeClass;
+ protected final Class 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 messageTypeClass, Class 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 previousSubscriptions,
+ @NotNull Set newSubscriptions, @NotNull Function, S> subscriptionUpdateObjectCreator) {
+ // Unsubscribe from previous subscriptions
+ final Set unsubscribeSet = difference(previousSubscriptions, newSubscriptions);
+ if (!unsubscribeSet.isEmpty()) {
+ sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(unsubscribeSet), false);
+ }
+ // Subscribe to new subscriptions
+ final Set 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 null.
+ * Whether the given messageType 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 newSubscribedSymbols, Set 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 messageType 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 tradeSymbols, Collection quoteSymbols,
- Collection barSymbols) {
- sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, true);
- }
-
- @Override
- public void unsubscribe(Collection tradeSymbols, Collection quoteSymbols,
- Collection barSymbols) {
- sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, false);
- }
+ protected abstract boolean isErrorMessageType(T messageType);
/**
- * Sends a subscription update request.
+ * Whether the given messageType is "subscription".
*
- * @param tradeSymbols a {@link Collection} of symbols to update for trades or null for no change
- * @param quoteSymbols a {@link Collection} of symbols to update for quotes or null for no change
- * @param barSymbols a {@link Collection} of symbols to update for bars or null for no change
- * @param subscribe true to subscribe, false to unsubscribe
+ * @param messageType the message type
+ *
+ * @return a boolean
*/
- private void sendSubscriptionUpdate(Collection tradeSymbols, Collection quoteSymbols,
- Collection 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 symbols to subscriptionUpdateObject with elementKey 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 symbols) {
- if (symbols != null && !symbols.isEmpty()) {
- JsonArray symbolArray = new JsonArray();
- symbols.forEach(symbolArray::add);
- subscriptionUpdateObject.add(elementKey, symbolArray);
- }
- }
-
- @Override
- public Collection subscribedControls() {
- return new HashSet<>(listenedMarketDataMessageTypes);
- }
-
- @Override
- public Collection subscribedTrades() {
- return new HashSet<>(subscribedTrades);
- }
-
- @Override
- public Collection subscribedQuotes() {
- return new HashSet<>(subscribedQuotes);
- }
-
- @Override
- public Collection 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 {
-
- /**
- * 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.
- *
- * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to subscribe to
- * ALL available symbols.
- *
- * 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 null for no change
- * @param quoteSymbols a {@link Collection} of symbols to subscribe to quotes or null for no change
- * @param barSymbols a {@link Collection} of symbols to subscribe to bars or null for no change
- *
- * @see #unsubscribe(Collection, Collection, Collection)
- */
- void subscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols);
-
- /**
- * Unsubscribes from trades, quotes, or bars according to the given {@link Collection} of symbols.
- *
- * 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 null for no
- * change
- * @param quoteSymbols a {@link Collection} of symbols to unsubscribe from quotes or null for no
- * change
- * @param barSymbols a {@link Collection} of symbols to unsubscribe from bars or null for no change
- *
- * @see #subscribe(Collection, Collection, Collection)
- */
- void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols);
-
- /**
- * Gets all of the currently subscribed control {@link MarketDataMessageType}s.
- *
- * @return a {@link Collection} of {@link MarketDataMessageType}s
- */
- Collection subscribedControls();
-
- /**
- * Gets all of the currently subscribed symbols for trade updates.
- *
- * @return a {@link Collection} of {@link String}s
- */
- Collection subscribedTrades();
-
- /**
- * Gets all of the currently subscribed symbols for quote updates.
- *
- * @return a {@link Collection} of {@link String}s
- */
- Collection subscribedQuotes();
-
- /**
- * Gets all of the currently subscribed symbols for bar updates.
- *
- * @return a {@link Collection} of {@link String}s
- */
- Collection 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 Real-time Data.",
"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
+ 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 symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getTradeSubscriptions(), symbols,
+ set -> newEmptyCryptoSubscriptionsMessage().withTrades(set));
+ }
+
+ @Override
+ public Set getTradeSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades();
+ }
+
+ @Override
+ public void setQuoteSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getQuoteSubscriptions(), symbols,
+ set -> newEmptyCryptoSubscriptionsMessage().withQuotes(set));
+ }
+
+ @Override
+ public Set getQuoteSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes();
+ }
+
+ @Override
+ public void setMinuteBarSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getMinuteBarSubscriptions(), symbols,
+ set -> newEmptyCryptoSubscriptionsMessage().withMinuteBars(set));
+ }
+
+ @Override
+ public Set getMinuteBarSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars();
+ }
+
+ @Override
+ public void setDailyBarSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getDailyBarSubscriptions(), symbols,
+ set -> newEmptyCryptoSubscriptionsMessage().withDailyBars(set));
+ }
+
+ @Override
+ public Set getDailyBarSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars();
+ }
+
+ @Override
+ public void setUpdatedBarSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getUpdatedBarSubscriptions(), symbols,
+ set -> newEmptyCryptoSubscriptionsMessage().withUpdatedBars(set));
+ }
+
+ @Override
+ public Set getUpdatedBarSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars();
+ }
+
+ @Override
+ public void setOrderBookSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getOrderBookSubscriptions(), symbols,
+ set -> newEmptyCryptoSubscriptionsMessage().withOrderBooks(set));
+ }
+
+ @Override
+ public Set 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 symbols 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 null to unsubscribe from all symbols
+ */
+ void setTradeSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#TRADES}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getTradeSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setQuoteSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#QUOTES}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getQuoteSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setMinuteBarSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#MINUTE_BARS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getMinuteBarSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setDailyBarSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#DAILY_BARS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getDailyBarSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setUpdatedBarSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#UPDATED_BARS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getUpdatedBarSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setOrderBookSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#ORDER_BOOKS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set 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",
+ "title": "The {@link java.util.Set} of symbols subscribed to trades."
+ },
+ "quotes": {
+ "existingJavaType": "java.util.Set",
+ "title": "The {@link java.util.Set} of symbols subscribed to quotes."
+ },
+ "bars": {
+ "existingJavaType": "java.util.Set",
+ "javaName": "minuteBars",
+ "title": "The {@link java.util.Set} of symbols subscribed to minute bars."
+ },
+ "dailyBars": {
+ "existingJavaType": "java.util.Set",
+ "title": "The {@link java.util.Set} of symbols subscribed to daily bars."
+ },
+ "updatedBars": {
+ "existingJavaType": "java.util.Set",
+ "title": "The {@link java.util.Set} of symbols subscribed to updated bars."
+ },
+ "orderbooks": {
+ "existingJavaType": "java.util.Set",
+ "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 Real-time Data.",
"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",
+ "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",
+ "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 true for the first message and false 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
+ 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 symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getNewsSubscriptions(), symbols,
+ set -> new NewsSubscriptionsMessage().withNews(set));
+ }
+
+ @Override
+ public Set 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 symbols 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 null to unsubscribe from all symbols
+ */
+ void setNewsSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link NewsMarketDataMessageType#NEWS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set 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",
+ "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"
+ },
+ "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
+ 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 symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getTradeSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withTrades(set));
+ }
+
+ @Override
+ public Set getTradeSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades();
+ }
+
+ @Override
+ public void setQuoteSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getQuoteSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withQuotes(set));
+ }
+
+ @Override
+ public Set getQuoteSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes();
+ }
+
+ @Override
+ public void setMinuteBarSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getMinuteBarSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withMinuteBars(set));
+ }
+
+ @Override
+ public Set getMinuteBarSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars();
+ }
+
+ @Override
+ public void setDailyBarSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getDailyBarSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withDailyBars(set));
+ }
+
+ @Override
+ public Set getDailyBarSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars();
+ }
+
+ @Override
+ public void setUpdatedBarSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getUpdatedBarSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withUpdatedBars(set));
+ }
+
+ @Override
+ public Set getUpdatedBarSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars();
+ }
+
+ @Override
+ public void setLimitUpLimitDownBandSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getLimitUpLimitDownBandSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withLimitUpLimitDownBands(set));
+ }
+
+ @Override
+ public Set getLimitUpLimitDownBandSubscriptions() {
+ return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getLimitUpLimitDownBands();
+ }
+
+ @Override
+ public void setTradingStatuseSubscriptions(Set symbols) {
+ symbols = symbols == null ? Set.of() : symbols;
+ setSubscriptions(getTradingStatuseSubscriptions(), symbols,
+ set -> newEmptyStockSubscriptionsMessage().withTradingStatuses(set));
+ }
+
+ @Override
+ public Set 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 symbols to {@link StockMarketDataMessageType#TRADES}. This will remove all
+ * previous {@link StockMarketDataMessageType#TRADES} subscriptions.
+ *
+ * Note that this will automatically apply the same symbols 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 null to unsubscribe from all symbols
+ */
+ void setTradeSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADES}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getTradeSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setQuoteSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#QUOTES}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getQuoteSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setMinuteBarSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#MINUTE_BARS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getMinuteBarSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setDailyBarSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#DAILY_BARS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getDailyBarSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setUpdatedBarSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#UPDATED_BARS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getUpdatedBarSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setLimitUpLimitDownBandSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set getLimitUpLimitDownBandSubscriptions();
+
+ /**
+ * Subscribes the given symbols 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 null to unsubscribe from all symbols
+ */
+ void setTradingStatuseSubscriptions(Set symbols);
+
+ /**
+ * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADING_STATUSES}.
+ *
+ * @return a {@link Set} of {@link String} symbols
+ */
+ Set 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",
+ "title": "The {@link java.util.Set} of symbols subscribed to trades."
+ },
+ "quotes": {
+ "existingJavaType": "java.util.Set",
+ "title": "The {@link java.util.Set} of symbols subscribed to quotes."
+ },
+ "bars": {
+ "existingJavaType": "java.util.Set",
+ "javaName": "minuteBars",
+ "title": "The {@link java.util.Set} of symbols subscribed to minute bars."
+ },
+ "dailyBars": {
+ "existingJavaType": "java.util.Set",
+ "title": "The {@link java.util.Set} of symbols subscribed to daily bars."
+ },
+ "updatedBars": {
+ "existingJavaType": "java.util.Set",
+ "title": "The {@link java.util.Set} of symbols subscribed to updated bars."
+ },
+ "lulds": {
+ "existingJavaType": "java.util.Set",
+ "javaName": "limitUpLimitDownBands",
+ "title": "The {@link java.util.Set} of symbols subscribed to limit up - limit down bands."
+ },
+ "statuses": {
+ "existingJavaType": "java.util.Set",
+ "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",
+ "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",
+ "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",
+ "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",
+ "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 {
-}
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
- * Streaming
- */
-public class StreamingWebsocket extends AlpacaWebsocket
- implements StreamingWebsocketInterface {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(StreamingWebsocket.class);
- private static final String STREAM_ELEMENT_KEY = "stream";
- private static final List SUBSCRIBABLE_STREAMING_MESSAGE_TYPES = Collections.singletonList(
- StreamingMessageType.TRADE_UPDATES);
-
- /**
- * Creates a {@link HttpUrl} for {@link StreamingWebsocket} with the given alpacaSubdomain.
- *
- * @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 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 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 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 {
-
- /**
- * Sets the {@link StreamingMessageType}s to streamingMessageTypes.
- *
- * 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 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
+ * Updates Streaming.
+ */
+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 true to subscribe, false 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 Streaming.",
"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",
+ "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 Orders.",
"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 =
-#secret_key =
-#endpoint_api_type =
-#data_api_type =
-#user_agent =
-
-#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
- * alpaca.properties 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 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 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