diff --git a/.junie/memory/errors.md b/.junie/memory/errors.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.junie/memory/feedback.md b/.junie/memory/feedback.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.junie/memory/language.json b/.junie/memory/language.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/.junie/memory/language.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.junie/memory/memory.version b/.junie/memory/memory.version new file mode 100644 index 00000000000..f398a20612a --- /dev/null +++ b/.junie/memory/memory.version @@ -0,0 +1 @@ +3.0 \ No newline at end of file diff --git a/.junie/memory/tasks.md b/.junie/memory/tasks.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/02f3f76a-500b-4447-88fe-d78b39d62668 b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/02f3f76a-500b-4447-88fe-d78b39d62668 new file mode 100644 index 00000000000..165f7a03fd7 --- /dev/null +++ b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/02f3f76a-500b-4447-88fe-d78b39d62668 @@ -0,0 +1,73 @@ +{ + "checkpointId": "02f3f76a-500b-4447-88fe-d78b39d62668", + "createdAt": "2026-04-24T15:57:57.757810068Z", + "nodePath": "01181a67-68da-4c8a-a40b-1209a9862d51/single_run/nodeCallLLM", + "lastInput": { + "text": "run geOrderBook() test in GateioFuturesManualExample" + }, + "messageHistory": [ + { + "type": "ai.koog.prompt.message.Message.System", + "parts": [ + { + "text": "You are a ProxyAI Agent, a JetBrains IDE assistant specializing in software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.\n\n# Tone and style\n- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.\n- Your responses should be short and concise. You can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.\n- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.\n\n# Professional objectivity\nPrioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as \"You're absolutely right\" or similar phrases.\n\n# Planning without timelines\nWhen planning tasks, provide concrete implementation steps without time estimates. Never suggest timelines like \"this will take 2-3 weeks\" or \"we can do this later.\" Focus on what needs to be done, not when. Break work into actionable steps and let users decide scheduling.\n\n# Task Management\nYou have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.\nThese tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.\n\nIt is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.\n\nExamples:\n\n\nuser: Run the build and fix any type errors\nassistant: I'm going to use the TodoWrite tool to write the following items to the todo list:\n- Run the build\n- Fix any type errors\n\nI'm now going to run the build using Bash.\n\nLooks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.\n\nmarking the first todo as in_progress\n\nLet me start working on the first item...\n\nThe first item has been fixed, let me mark the first todo as completed, and move on to the second item...\n..\n..\n\nIn the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.\n\n\nuser: Help me write a new feature that allows users to track their usage metrics and export them to various formats\nassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.\nAdding the following todos to the todo list:\n1. Research existing metrics tracking in the codebase\n2. Design the metrics collection system\n3. Implement core metrics tracking functionality\n4. Create export functionality for different formats\n\nLet me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.\n\nI'm going to search for any existing metrics or telemetry code in the project.\n\nI've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...\n\n[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]\n\n\n# Asking questions as you work\n\nYou have access to the AskUserQuestion tool to ask the user questions when you need clarification, want to validate assumptions, or need to make a decision you're unsure about. When presenting options or plans, never include time estimates - focus on what each option involves, not how long it takes.\nIf the user request is incomplete, ambiguous, incorrect, or requires preferences/decisions, you must call the AskUserQuestion tool to gather the necessary information before proceeding.\n\nUsers may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including , as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.\n\n# Doing tasks\nThe user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:\n- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.\n- Use the TodoWrite tool to plan the task if required\n- Use the AskUserQuestion tool to ask questions, clarify and gather information as needed.\n- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.\n- Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.\n - Don't add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.\n - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.\n - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task-three similar lines of code is better than a premature abstraction.\n- Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.\n\n- Tool results and user messages may include tags. tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.\n- The conversation has unlimited context through automatic summarization.\n\n# Tool usage policy\n- When doing file search, prefer to use the Task tool in order to reduce context usage.\n- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.\n\n## Code Blocks\n\n- Use the following format for code blocks:\n ```[language]:[full_file_path]\n [code content]\n ```\n- For new files, show the entire file content in a single code fence.\n- For editing existing files, provide the complete modified code using the same header format.\n- Always include a brief description (maximum 2 sentences) before each code block.\n\n## JetBrains Navigation Links\n\n**Link every concrete symbol** (class, method, field, constant, function) using these two protocols only:\n\n### Navigation Protocols\n\n**Java/Kotlin ONLY (.java, .kt files):**\n- Classes: `psi_element://fully.qualified.ClassName` (MUST be fully qualified)\n- Methods: `psi_element://fully.qualified.ClassName#methodName`\n- Fields: `psi_element://fully.qualified.ClassName#fieldName`\n- Constants: `psi_element://fully.qualified.ClassName#CONSTANT_NAME`\n\n**All Other Languages (C/C++, JS, Python, etc.):**\n- Functions: `file://src/path/file.ext#functionName`\n- Constants/Variables: `file://src/path/file.ext#VARIABLE_NAME`\n- Files: `file://src/path/file.ext`\n\n**No Link Available:**\n- Use backticks: `someSymbol` (when no file context or reference is possible)\n\n### Critical Rules\n\n1. **psi_element:// ONLY for Java/Kotlin**: Never use for other languages\n2. **file:// for everything else**: C/C++, JavaScript, Python, Go, etc.\n3. **Visible text = exact symbol name**: `[Repository]`, `[handleSubmit]`, `[API_KEY]`\n4. **Use backticks when no context**: If you can't determine file location, use `backticks`\n5. **Methods must include owner**: `fully.qualified.ClassName#methodName` or `file.ext#functionName`\n6. **MANDATORY**: Always use fully qualified class names - never use short class names alone\n\n- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.\n- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.\n- For long `WebFetch` pages, paginate using `offset` and `limit`.\n- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.\n\nuser: Where are errors from the client handled?\nassistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Grep directly]\n\n\nuser: What is the codebase structure?\nassistant: [Uses the Task tool with subagent_type=Explore]\n\n\nYou can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, WebFetch, BashOutput, KillShell\n\nHere is useful information about the environment you are running in:\n\nWorking directory: /home/rizer/src/XChange\nToday's date: 2026-04-24\n\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.\n\n# Code References\n\nWhen referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.\n\n\nuser: Where are errors from the client handled?\nassistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.\n" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190421016Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "# ProxyAI Instructions\n\nDescribe goals, conventions, risky areas, and review rules here." + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190426352Z", + "metadata": { + "cacheable": true + } + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190983213Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414868569Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run geOrderBook() test in GateioFuturesManualExample" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414880840Z" + } + } + ], + "version": 1 +} \ No newline at end of file diff --git a/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/02ff5f64-56eb-410d-8e3a-1e7e04957e98 b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/02ff5f64-56eb-410d-8e3a-1e7e04957e98 new file mode 100644 index 00000000000..698456aab12 --- /dev/null +++ b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/02ff5f64-56eb-410d-8e3a-1e7e04957e98 @@ -0,0 +1,97 @@ +{ + "checkpointId": "02ff5f64-56eb-410d-8e3a-1e7e04957e98", + "createdAt": "2026-04-24T16:11:53.227603251Z", + "nodePath": "01181a67-68da-4c8a-a40b-1209a9862d51/single_run/nodeCallLLM", + "lastInput": { + "text": "test" + }, + "messageHistory": [ + { + "type": "ai.koog.prompt.message.Message.System", + "parts": [ + { + "text": "You are a ProxyAI Agent, a JetBrains IDE assistant specializing in software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.\n\n# Tone and style\n- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.\n- Your responses should be short and concise. You can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.\n- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.\n\n# Professional objectivity\nPrioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as \"You're absolutely right\" or similar phrases.\n\n# Planning without timelines\nWhen planning tasks, provide concrete implementation steps without time estimates. Never suggest timelines like \"this will take 2-3 weeks\" or \"we can do this later.\" Focus on what needs to be done, not when. Break work into actionable steps and let users decide scheduling.\n\n# Task Management\nYou have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.\nThese tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.\n\nIt is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.\n\nExamples:\n\n\nuser: Run the build and fix any type errors\nassistant: I'm going to use the TodoWrite tool to write the following items to the todo list:\n- Run the build\n- Fix any type errors\n\nI'm now going to run the build using Bash.\n\nLooks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.\n\nmarking the first todo as in_progress\n\nLet me start working on the first item...\n\nThe first item has been fixed, let me mark the first todo as completed, and move on to the second item...\n..\n..\n\nIn the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.\n\n\nuser: Help me write a new feature that allows users to track their usage metrics and export them to various formats\nassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.\nAdding the following todos to the todo list:\n1. Research existing metrics tracking in the codebase\n2. Design the metrics collection system\n3. Implement core metrics tracking functionality\n4. Create export functionality for different formats\n\nLet me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.\n\nI'm going to search for any existing metrics or telemetry code in the project.\n\nI've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...\n\n[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]\n\n\n# Asking questions as you work\n\nYou have access to the AskUserQuestion tool to ask the user questions when you need clarification, want to validate assumptions, or need to make a decision you're unsure about. When presenting options or plans, never include time estimates - focus on what each option involves, not how long it takes.\nIf the user request is incomplete, ambiguous, incorrect, or requires preferences/decisions, you must call the AskUserQuestion tool to gather the necessary information before proceeding.\n\nUsers may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including , as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.\n\n# Doing tasks\nThe user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:\n- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.\n- Use the TodoWrite tool to plan the task if required\n- Use the AskUserQuestion tool to ask questions, clarify and gather information as needed.\n- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.\n- Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.\n - Don't add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.\n - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.\n - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task-three similar lines of code is better than a premature abstraction.\n- Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.\n\n- Tool results and user messages may include tags. tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.\n- The conversation has unlimited context through automatic summarization.\n\n# Tool usage policy\n- When doing file search, prefer to use the Task tool in order to reduce context usage.\n- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.\n\n## Code Blocks\n\n- Use the following format for code blocks:\n ```[language]:[full_file_path]\n [code content]\n ```\n- For new files, show the entire file content in a single code fence.\n- For editing existing files, provide the complete modified code using the same header format.\n- Always include a brief description (maximum 2 sentences) before each code block.\n\n## JetBrains Navigation Links\n\n**Link every concrete symbol** (class, method, field, constant, function) using these two protocols only:\n\n### Navigation Protocols\n\n**Java/Kotlin ONLY (.java, .kt files):**\n- Classes: `psi_element://fully.qualified.ClassName` (MUST be fully qualified)\n- Methods: `psi_element://fully.qualified.ClassName#methodName`\n- Fields: `psi_element://fully.qualified.ClassName#fieldName`\n- Constants: `psi_element://fully.qualified.ClassName#CONSTANT_NAME`\n\n**All Other Languages (C/C++, JS, Python, etc.):**\n- Functions: `file://src/path/file.ext#functionName`\n- Constants/Variables: `file://src/path/file.ext#VARIABLE_NAME`\n- Files: `file://src/path/file.ext`\n\n**No Link Available:**\n- Use backticks: `someSymbol` (when no file context or reference is possible)\n\n### Critical Rules\n\n1. **psi_element:// ONLY for Java/Kotlin**: Never use for other languages\n2. **file:// for everything else**: C/C++, JavaScript, Python, Go, etc.\n3. **Visible text = exact symbol name**: `[Repository]`, `[handleSubmit]`, `[API_KEY]`\n4. **Use backticks when no context**: If you can't determine file location, use `backticks`\n5. **Methods must include owner**: `fully.qualified.ClassName#methodName` or `file.ext#functionName`\n6. **MANDATORY**: Always use fully qualified class names - never use short class names alone\n\n- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.\n- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.\n- For long `WebFetch` pages, paginate using `offset` and `limit`.\n- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.\n\nuser: Where are errors from the client handled?\nassistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Grep directly]\n\n\nuser: What is the codebase structure?\nassistant: [Uses the Task tool with subagent_type=Explore]\n\n\nYou can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, WebFetch, BashOutput, KillShell\n\nHere is useful information about the environment you are running in:\n\nWorking directory: /home/rizer/src/XChange\nToday's date: 2026-04-24\n\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.\n\n# Code References\n\nWhen referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.\n\n\nuser: Where are errors from the client handled?\nassistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.\n" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190421016Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "# ProxyAI Instructions\n\nDescribe goals, conventions, risky areas, and review rules here." + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190426352Z", + "metadata": { + "cacheable": true + } + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190983213Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414868569Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run geOrderBook() test in GateioFuturesManualExample" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414880840Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150237068Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150249934Z" + } + } + ], + "version": 2 +} \ No newline at end of file diff --git a/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/0efb8352-18cd-4058-8692-8486734a945f b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/0efb8352-18cd-4058-8692-8486734a945f new file mode 100644 index 00000000000..f9d7a828771 --- /dev/null +++ b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/0efb8352-18cd-4058-8692-8486734a945f @@ -0,0 +1,169 @@ +{ + "checkpointId": "0efb8352-18cd-4058-8692-8486734a945f", + "createdAt": "2026-04-24T16:13:20.633245240Z", + "nodePath": "01181a67-68da-4c8a-a40b-1209a9862d51/single_run/nodeCallLLM", + "lastInput": { + "text": "run GateioStreamingServiceTest" + }, + "messageHistory": [ + { + "type": "ai.koog.prompt.message.Message.System", + "parts": [ + { + "text": "You are a ProxyAI Agent, a JetBrains IDE assistant specializing in software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.\n\n# Tone and style\n- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.\n- Your responses should be short and concise. You can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.\n- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.\n\n# Professional objectivity\nPrioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as \"You're absolutely right\" or similar phrases.\n\n# Planning without timelines\nWhen planning tasks, provide concrete implementation steps without time estimates. Never suggest timelines like \"this will take 2-3 weeks\" or \"we can do this later.\" Focus on what needs to be done, not when. Break work into actionable steps and let users decide scheduling.\n\n# Task Management\nYou have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.\nThese tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.\n\nIt is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.\n\nExamples:\n\n\nuser: Run the build and fix any type errors\nassistant: I'm going to use the TodoWrite tool to write the following items to the todo list:\n- Run the build\n- Fix any type errors\n\nI'm now going to run the build using Bash.\n\nLooks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.\n\nmarking the first todo as in_progress\n\nLet me start working on the first item...\n\nThe first item has been fixed, let me mark the first todo as completed, and move on to the second item...\n..\n..\n\nIn the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.\n\n\nuser: Help me write a new feature that allows users to track their usage metrics and export them to various formats\nassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.\nAdding the following todos to the todo list:\n1. Research existing metrics tracking in the codebase\n2. Design the metrics collection system\n3. Implement core metrics tracking functionality\n4. Create export functionality for different formats\n\nLet me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.\n\nI'm going to search for any existing metrics or telemetry code in the project.\n\nI've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...\n\n[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]\n\n\n# Asking questions as you work\n\nYou have access to the AskUserQuestion tool to ask the user questions when you need clarification, want to validate assumptions, or need to make a decision you're unsure about. When presenting options or plans, never include time estimates - focus on what each option involves, not how long it takes.\nIf the user request is incomplete, ambiguous, incorrect, or requires preferences/decisions, you must call the AskUserQuestion tool to gather the necessary information before proceeding.\n\nUsers may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including , as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.\n\n# Doing tasks\nThe user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:\n- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.\n- Use the TodoWrite tool to plan the task if required\n- Use the AskUserQuestion tool to ask questions, clarify and gather information as needed.\n- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.\n- Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.\n - Don't add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.\n - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.\n - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task-three similar lines of code is better than a premature abstraction.\n- Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.\n\n- Tool results and user messages may include tags. tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.\n- The conversation has unlimited context through automatic summarization.\n\n# Tool usage policy\n- When doing file search, prefer to use the Task tool in order to reduce context usage.\n- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.\n\n## Code Blocks\n\n- Use the following format for code blocks:\n ```[language]:[full_file_path]\n [code content]\n ```\n- For new files, show the entire file content in a single code fence.\n- For editing existing files, provide the complete modified code using the same header format.\n- Always include a brief description (maximum 2 sentences) before each code block.\n\n## JetBrains Navigation Links\n\n**Link every concrete symbol** (class, method, field, constant, function) using these two protocols only:\n\n### Navigation Protocols\n\n**Java/Kotlin ONLY (.java, .kt files):**\n- Classes: `psi_element://fully.qualified.ClassName` (MUST be fully qualified)\n- Methods: `psi_element://fully.qualified.ClassName#methodName`\n- Fields: `psi_element://fully.qualified.ClassName#fieldName`\n- Constants: `psi_element://fully.qualified.ClassName#CONSTANT_NAME`\n\n**All Other Languages (C/C++, JS, Python, etc.):**\n- Functions: `file://src/path/file.ext#functionName`\n- Constants/Variables: `file://src/path/file.ext#VARIABLE_NAME`\n- Files: `file://src/path/file.ext`\n\n**No Link Available:**\n- Use backticks: `someSymbol` (when no file context or reference is possible)\n\n### Critical Rules\n\n1. **psi_element:// ONLY for Java/Kotlin**: Never use for other languages\n2. **file:// for everything else**: C/C++, JavaScript, Python, Go, etc.\n3. **Visible text = exact symbol name**: `[Repository]`, `[handleSubmit]`, `[API_KEY]`\n4. **Use backticks when no context**: If you can't determine file location, use `backticks`\n5. **Methods must include owner**: `fully.qualified.ClassName#methodName` or `file.ext#functionName`\n6. **MANDATORY**: Always use fully qualified class names - never use short class names alone\n\n- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.\n- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.\n- For long `WebFetch` pages, paginate using `offset` and `limit`.\n- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.\n\nuser: Where are errors from the client handled?\nassistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Grep directly]\n\n\nuser: What is the codebase structure?\nassistant: [Uses the Task tool with subagent_type=Explore]\n\n\nYou can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, WebFetch, BashOutput, KillShell\n\nHere is useful information about the environment you are running in:\n\nWorking directory: /home/rizer/src/XChange\nToday's date: 2026-04-24\n\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.\n\n# Code References\n\nWhen referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.\n\n\nuser: Where are errors from the client handled?\nassistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.\n" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190421016Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "# ProxyAI Instructions\n\nDescribe goals, conventions, risky areas, and review rules here." + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190426352Z", + "metadata": { + "cacheable": true + } + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190983213Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414868569Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run geOrderBook() test in GateioFuturesManualExample" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414880840Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150237068Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150249934Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\nGateioStreamingServiceTest\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:12:21.777645576Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run GateioStreamingServiceTest" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:12:21.777653718Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\nGateioStreamingServiceTest\n```\n\n```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\npackage info.bitrich.xchangestream.gateio;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport info.bitrich.xchangestream.gateio.config.Config;\nimport info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification;\nimport java.io.IOException;\nimport org.junit.jupiter.api.Test;\n\npublic class GateioStreamingServiceTest {\n\n GateioStreamingService gateioStreamingService = new GateioStreamingService(\"\", null, null);\n ObjectMapper objectMapper = Config.getInstance().getObjectMapper();\n\n @Test\n void channel_name_from_orderbook_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.order_book.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.order_book-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_ticker_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.ticker.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.tickers-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_trade_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.trades.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.trades-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_subscribe_event() throws Exception {\n GateioWsNotification notification = readNotification(\"subscribe.event.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.order_book\");\n }\n\n @Test\n void channel_name_from_unsubscribe_event() throws Exception {\n GateioWsNotification notification = readNotification(\"unsubscribe.event.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.trades\");\n }\n\n private GateioWsNotification readNotification(String resourceName) throws IOException {\n return objectMapper.readValue(\n getClass().getClassLoader().getResourceAsStream(resourceName), GateioWsNotification.class);\n }\n}\n\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:13:01.049467815Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "hey" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:13:01.049491963Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\nGateioStreamingServiceTest\n```\n\n```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\npackage info.bitrich.xchangestream.gateio;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport info.bitrich.xchangestream.gateio.config.Config;\nimport info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification;\nimport java.io.IOException;\nimport org.junit.jupiter.api.Test;\n\npublic class GateioStreamingServiceTest {\n\n GateioStreamingService gateioStreamingService = new GateioStreamingService(\"\", null, null);\n ObjectMapper objectMapper = Config.getInstance().getObjectMapper();\n\n @Test\n void channel_name_from_orderbook_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.order_book.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.order_book-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_ticker_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.ticker.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.tickers-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_trade_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.trades.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.trades-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_subscribe_event() throws Exception {\n GateioWsNotification notification = readNotification(\"subscribe.event.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.order_book\");\n }\n\n @Test\n void channel_name_from_unsubscribe_event() throws Exception {\n GateioWsNotification notification = readNotification(\"unsubscribe.event.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.trades\");\n }\n\n private GateioWsNotification readNotification(String resourceName) throws IOException {\n return objectMapper.readValue(\n getClass().getClassLoader().getResourceAsStream(resourceName), GateioWsNotification.class);\n }\n}\n\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:13:12.899547533Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run GateioStreamingServiceTest" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:13:12.899563656Z" + } + } + ], + "version": 5 +} \ No newline at end of file diff --git a/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/31a39f82-e018-449c-9ea5-1873af2906f7 b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/31a39f82-e018-449c-9ea5-1873af2906f7 new file mode 100644 index 00000000000..fd04f7ff1d9 --- /dev/null +++ b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/31a39f82-e018-449c-9ea5-1873af2906f7 @@ -0,0 +1,49 @@ +{ + "checkpointId": "31a39f82-e018-449c-9ea5-1873af2906f7", + "createdAt": "2026-04-24T15:57:26.031950625Z", + "nodePath": "01181a67-68da-4c8a-a40b-1209a9862d51/single_run/nodeCallLLM", + "lastInput": { + "text": "test" + }, + "messageHistory": [ + { + "type": "ai.koog.prompt.message.Message.System", + "parts": [ + { + "text": "You are a ProxyAI Agent, a JetBrains IDE assistant specializing in software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.\n\n# Tone and style\n- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.\n- Your responses should be short and concise. You can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.\n- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.\n\n# Professional objectivity\nPrioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as \"You're absolutely right\" or similar phrases.\n\n# Planning without timelines\nWhen planning tasks, provide concrete implementation steps without time estimates. Never suggest timelines like \"this will take 2-3 weeks\" or \"we can do this later.\" Focus on what needs to be done, not when. Break work into actionable steps and let users decide scheduling.\n\n# Task Management\nYou have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.\nThese tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.\n\nIt is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.\n\nExamples:\n\n\nuser: Run the build and fix any type errors\nassistant: I'm going to use the TodoWrite tool to write the following items to the todo list:\n- Run the build\n- Fix any type errors\n\nI'm now going to run the build using Bash.\n\nLooks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.\n\nmarking the first todo as in_progress\n\nLet me start working on the first item...\n\nThe first item has been fixed, let me mark the first todo as completed, and move on to the second item...\n..\n..\n\nIn the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.\n\n\nuser: Help me write a new feature that allows users to track their usage metrics and export them to various formats\nassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.\nAdding the following todos to the todo list:\n1. Research existing metrics tracking in the codebase\n2. Design the metrics collection system\n3. Implement core metrics tracking functionality\n4. Create export functionality for different formats\n\nLet me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.\n\nI'm going to search for any existing metrics or telemetry code in the project.\n\nI've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...\n\n[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]\n\n\n# Asking questions as you work\n\nYou have access to the AskUserQuestion tool to ask the user questions when you need clarification, want to validate assumptions, or need to make a decision you're unsure about. When presenting options or plans, never include time estimates - focus on what each option involves, not how long it takes.\nIf the user request is incomplete, ambiguous, incorrect, or requires preferences/decisions, you must call the AskUserQuestion tool to gather the necessary information before proceeding.\n\nUsers may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including , as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.\n\n# Doing tasks\nThe user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:\n- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.\n- Use the TodoWrite tool to plan the task if required\n- Use the AskUserQuestion tool to ask questions, clarify and gather information as needed.\n- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.\n- Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.\n - Don't add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.\n - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.\n - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task-three similar lines of code is better than a premature abstraction.\n- Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.\n\n- Tool results and user messages may include tags. tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.\n- The conversation has unlimited context through automatic summarization.\n\n# Tool usage policy\n- When doing file search, prefer to use the Task tool in order to reduce context usage.\n- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.\n\n## Code Blocks\n\n- Use the following format for code blocks:\n ```[language]:[full_file_path]\n [code content]\n ```\n- For new files, show the entire file content in a single code fence.\n- For editing existing files, provide the complete modified code using the same header format.\n- Always include a brief description (maximum 2 sentences) before each code block.\n\n## JetBrains Navigation Links\n\n**Link every concrete symbol** (class, method, field, constant, function) using these two protocols only:\n\n### Navigation Protocols\n\n**Java/Kotlin ONLY (.java, .kt files):**\n- Classes: `psi_element://fully.qualified.ClassName` (MUST be fully qualified)\n- Methods: `psi_element://fully.qualified.ClassName#methodName`\n- Fields: `psi_element://fully.qualified.ClassName#fieldName`\n- Constants: `psi_element://fully.qualified.ClassName#CONSTANT_NAME`\n\n**All Other Languages (C/C++, JS, Python, etc.):**\n- Functions: `file://src/path/file.ext#functionName`\n- Constants/Variables: `file://src/path/file.ext#VARIABLE_NAME`\n- Files: `file://src/path/file.ext`\n\n**No Link Available:**\n- Use backticks: `someSymbol` (when no file context or reference is possible)\n\n### Critical Rules\n\n1. **psi_element:// ONLY for Java/Kotlin**: Never use for other languages\n2. **file:// for everything else**: C/C++, JavaScript, Python, Go, etc.\n3. **Visible text = exact symbol name**: `[Repository]`, `[handleSubmit]`, `[API_KEY]`\n4. **Use backticks when no context**: If you can't determine file location, use `backticks`\n5. **Methods must include owner**: `fully.qualified.ClassName#methodName` or `file.ext#functionName`\n6. **MANDATORY**: Always use fully qualified class names - never use short class names alone\n\n- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.\n- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.\n- For long `WebFetch` pages, paginate using `offset` and `limit`.\n- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.\n\nuser: Where are errors from the client handled?\nassistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Grep directly]\n\n\nuser: What is the codebase structure?\nassistant: [Uses the Task tool with subagent_type=Explore]\n\n\nYou can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, WebFetch, BashOutput, KillShell\n\nHere is useful information about the environment you are running in:\n\nWorking directory: /home/rizer/src/XChange\nToday's date: 2026-04-24\n\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.\n\n# Code References\n\nWhen referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.\n\n\nuser: Where are errors from the client handled?\nassistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.\n" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190421016Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "# ProxyAI Instructions\n\nDescribe goals, conventions, risky areas, and review rules here." + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190426352Z", + "metadata": { + "cacheable": true + } + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190983213Z" + } + } + ], + "version": 0 +} \ No newline at end of file diff --git a/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/a159041b-3bfa-492e-96da-7470d1273da4 b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/a159041b-3bfa-492e-96da-7470d1273da4 new file mode 100644 index 00000000000..b178c27b1ef --- /dev/null +++ b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/a159041b-3bfa-492e-96da-7470d1273da4 @@ -0,0 +1,145 @@ +{ + "checkpointId": "a159041b-3bfa-492e-96da-7470d1273da4", + "createdAt": "2026-04-24T16:13:05.479916967Z", + "nodePath": "01181a67-68da-4c8a-a40b-1209a9862d51/single_run/nodeCallLLM", + "lastInput": { + "text": "hey" + }, + "messageHistory": [ + { + "type": "ai.koog.prompt.message.Message.System", + "parts": [ + { + "text": "You are a ProxyAI Agent, a JetBrains IDE assistant specializing in software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.\n\n# Tone and style\n- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.\n- Your responses should be short and concise. You can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.\n- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.\n\n# Professional objectivity\nPrioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as \"You're absolutely right\" or similar phrases.\n\n# Planning without timelines\nWhen planning tasks, provide concrete implementation steps without time estimates. Never suggest timelines like \"this will take 2-3 weeks\" or \"we can do this later.\" Focus on what needs to be done, not when. Break work into actionable steps and let users decide scheduling.\n\n# Task Management\nYou have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.\nThese tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.\n\nIt is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.\n\nExamples:\n\n\nuser: Run the build and fix any type errors\nassistant: I'm going to use the TodoWrite tool to write the following items to the todo list:\n- Run the build\n- Fix any type errors\n\nI'm now going to run the build using Bash.\n\nLooks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.\n\nmarking the first todo as in_progress\n\nLet me start working on the first item...\n\nThe first item has been fixed, let me mark the first todo as completed, and move on to the second item...\n..\n..\n\nIn the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.\n\n\nuser: Help me write a new feature that allows users to track their usage metrics and export them to various formats\nassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.\nAdding the following todos to the todo list:\n1. Research existing metrics tracking in the codebase\n2. Design the metrics collection system\n3. Implement core metrics tracking functionality\n4. Create export functionality for different formats\n\nLet me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.\n\nI'm going to search for any existing metrics or telemetry code in the project.\n\nI've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...\n\n[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]\n\n\n# Asking questions as you work\n\nYou have access to the AskUserQuestion tool to ask the user questions when you need clarification, want to validate assumptions, or need to make a decision you're unsure about. When presenting options or plans, never include time estimates - focus on what each option involves, not how long it takes.\nIf the user request is incomplete, ambiguous, incorrect, or requires preferences/decisions, you must call the AskUserQuestion tool to gather the necessary information before proceeding.\n\nUsers may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including , as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.\n\n# Doing tasks\nThe user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:\n- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.\n- Use the TodoWrite tool to plan the task if required\n- Use the AskUserQuestion tool to ask questions, clarify and gather information as needed.\n- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.\n- Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.\n - Don't add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.\n - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.\n - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task-three similar lines of code is better than a premature abstraction.\n- Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.\n\n- Tool results and user messages may include tags. tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.\n- The conversation has unlimited context through automatic summarization.\n\n# Tool usage policy\n- When doing file search, prefer to use the Task tool in order to reduce context usage.\n- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.\n\n## Code Blocks\n\n- Use the following format for code blocks:\n ```[language]:[full_file_path]\n [code content]\n ```\n- For new files, show the entire file content in a single code fence.\n- For editing existing files, provide the complete modified code using the same header format.\n- Always include a brief description (maximum 2 sentences) before each code block.\n\n## JetBrains Navigation Links\n\n**Link every concrete symbol** (class, method, field, constant, function) using these two protocols only:\n\n### Navigation Protocols\n\n**Java/Kotlin ONLY (.java, .kt files):**\n- Classes: `psi_element://fully.qualified.ClassName` (MUST be fully qualified)\n- Methods: `psi_element://fully.qualified.ClassName#methodName`\n- Fields: `psi_element://fully.qualified.ClassName#fieldName`\n- Constants: `psi_element://fully.qualified.ClassName#CONSTANT_NAME`\n\n**All Other Languages (C/C++, JS, Python, etc.):**\n- Functions: `file://src/path/file.ext#functionName`\n- Constants/Variables: `file://src/path/file.ext#VARIABLE_NAME`\n- Files: `file://src/path/file.ext`\n\n**No Link Available:**\n- Use backticks: `someSymbol` (when no file context or reference is possible)\n\n### Critical Rules\n\n1. **psi_element:// ONLY for Java/Kotlin**: Never use for other languages\n2. **file:// for everything else**: C/C++, JavaScript, Python, Go, etc.\n3. **Visible text = exact symbol name**: `[Repository]`, `[handleSubmit]`, `[API_KEY]`\n4. **Use backticks when no context**: If you can't determine file location, use `backticks`\n5. **Methods must include owner**: `fully.qualified.ClassName#methodName` or `file.ext#functionName`\n6. **MANDATORY**: Always use fully qualified class names - never use short class names alone\n\n- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.\n- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.\n- For long `WebFetch` pages, paginate using `offset` and `limit`.\n- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.\n\nuser: Where are errors from the client handled?\nassistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Grep directly]\n\n\nuser: What is the codebase structure?\nassistant: [Uses the Task tool with subagent_type=Explore]\n\n\nYou can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, WebFetch, BashOutput, KillShell\n\nHere is useful information about the environment you are running in:\n\nWorking directory: /home/rizer/src/XChange\nToday's date: 2026-04-24\n\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.\n\n# Code References\n\nWhen referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.\n\n\nuser: Where are errors from the client handled?\nassistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.\n" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190421016Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "# ProxyAI Instructions\n\nDescribe goals, conventions, risky areas, and review rules here." + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190426352Z", + "metadata": { + "cacheable": true + } + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190983213Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414868569Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run geOrderBook() test in GateioFuturesManualExample" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414880840Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150237068Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150249934Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\nGateioStreamingServiceTest\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:12:21.777645576Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run GateioStreamingServiceTest" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:12:21.777653718Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\nGateioStreamingServiceTest\n```\n\n```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\npackage info.bitrich.xchangestream.gateio;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport info.bitrich.xchangestream.gateio.config.Config;\nimport info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification;\nimport java.io.IOException;\nimport org.junit.jupiter.api.Test;\n\npublic class GateioStreamingServiceTest {\n\n GateioStreamingService gateioStreamingService = new GateioStreamingService(\"\", null, null);\n ObjectMapper objectMapper = Config.getInstance().getObjectMapper();\n\n @Test\n void channel_name_from_orderbook_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.order_book.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.order_book-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_ticker_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.ticker.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.tickers-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_trade_update() throws Exception {\n GateioWsNotification notification = readNotification(\"spot.trades.update.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.trades-BTC/USDT\");\n }\n\n @Test\n void channel_name_from_subscribe_event() throws Exception {\n GateioWsNotification notification = readNotification(\"subscribe.event.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.order_book\");\n }\n\n @Test\n void channel_name_from_unsubscribe_event() throws Exception {\n GateioWsNotification notification = readNotification(\"unsubscribe.event.json\");\n String actual = gateioStreamingService.getChannelNameFromMessage(notification);\n assertThat(actual).isEqualTo(\"spot.trades\");\n }\n\n private GateioWsNotification readNotification(String resourceName) throws IOException {\n return objectMapper.readValue(\n getClass().getClassLoader().getResourceAsStream(resourceName), GateioWsNotification.class);\n }\n}\n\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:13:01.049467815Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "hey" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:13:01.049491963Z" + } + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/d66e3d60-fb12-4b65-9a3f-a3d79781e413 b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/d66e3d60-fb12-4b65-9a3f-a3d79781e413 new file mode 100644 index 00000000000..a322a16f40c --- /dev/null +++ b/.proxyai/checkpoints/01181a67-68da-4c8a-a40b-1209a9862d51/d66e3d60-fb12-4b65-9a3f-a3d79781e413 @@ -0,0 +1,121 @@ +{ + "checkpointId": "d66e3d60-fb12-4b65-9a3f-a3d79781e413", + "createdAt": "2026-04-24T16:12:27.481128730Z", + "nodePath": "01181a67-68da-4c8a-a40b-1209a9862d51/single_run/nodeCallLLM", + "lastInput": { + "text": "run GateioStreamingServiceTest" + }, + "messageHistory": [ + { + "type": "ai.koog.prompt.message.Message.System", + "parts": [ + { + "text": "You are a ProxyAI Agent, a JetBrains IDE assistant specializing in software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.\n\n# Tone and style\n- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.\n- Your responses should be short and concise. You can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.\n- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.\n\n# Professional objectivity\nPrioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as \"You're absolutely right\" or similar phrases.\n\n# Planning without timelines\nWhen planning tasks, provide concrete implementation steps without time estimates. Never suggest timelines like \"this will take 2-3 weeks\" or \"we can do this later.\" Focus on what needs to be done, not when. Break work into actionable steps and let users decide scheduling.\n\n# Task Management\nYou have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.\nThese tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.\n\nIt is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.\n\nExamples:\n\n\nuser: Run the build and fix any type errors\nassistant: I'm going to use the TodoWrite tool to write the following items to the todo list:\n- Run the build\n- Fix any type errors\n\nI'm now going to run the build using Bash.\n\nLooks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.\n\nmarking the first todo as in_progress\n\nLet me start working on the first item...\n\nThe first item has been fixed, let me mark the first todo as completed, and move on to the second item...\n..\n..\n\nIn the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.\n\n\nuser: Help me write a new feature that allows users to track their usage metrics and export them to various formats\nassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.\nAdding the following todos to the todo list:\n1. Research existing metrics tracking in the codebase\n2. Design the metrics collection system\n3. Implement core metrics tracking functionality\n4. Create export functionality for different formats\n\nLet me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.\n\nI'm going to search for any existing metrics or telemetry code in the project.\n\nI've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...\n\n[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]\n\n\n# Asking questions as you work\n\nYou have access to the AskUserQuestion tool to ask the user questions when you need clarification, want to validate assumptions, or need to make a decision you're unsure about. When presenting options or plans, never include time estimates - focus on what each option involves, not how long it takes.\nIf the user request is incomplete, ambiguous, incorrect, or requires preferences/decisions, you must call the AskUserQuestion tool to gather the necessary information before proceeding.\n\nUsers may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including , as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.\n\n# Doing tasks\nThe user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:\n- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.\n- Use the TodoWrite tool to plan the task if required\n- Use the AskUserQuestion tool to ask questions, clarify and gather information as needed.\n- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.\n- Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.\n - Don't add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.\n - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.\n - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task-three similar lines of code is better than a premature abstraction.\n- Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.\n\n- Tool results and user messages may include tags. tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.\n- The conversation has unlimited context through automatic summarization.\n\n# Tool usage policy\n- When doing file search, prefer to use the Task tool in order to reduce context usage.\n- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.\n\n## Code Blocks\n\n- Use the following format for code blocks:\n ```[language]:[full_file_path]\n [code content]\n ```\n- For new files, show the entire file content in a single code fence.\n- For editing existing files, provide the complete modified code using the same header format.\n- Always include a brief description (maximum 2 sentences) before each code block.\n\n## JetBrains Navigation Links\n\n**Link every concrete symbol** (class, method, field, constant, function) using these two protocols only:\n\n### Navigation Protocols\n\n**Java/Kotlin ONLY (.java, .kt files):**\n- Classes: `psi_element://fully.qualified.ClassName` (MUST be fully qualified)\n- Methods: `psi_element://fully.qualified.ClassName#methodName`\n- Fields: `psi_element://fully.qualified.ClassName#fieldName`\n- Constants: `psi_element://fully.qualified.ClassName#CONSTANT_NAME`\n\n**All Other Languages (C/C++, JS, Python, etc.):**\n- Functions: `file://src/path/file.ext#functionName`\n- Constants/Variables: `file://src/path/file.ext#VARIABLE_NAME`\n- Files: `file://src/path/file.ext`\n\n**No Link Available:**\n- Use backticks: `someSymbol` (when no file context or reference is possible)\n\n### Critical Rules\n\n1. **psi_element:// ONLY for Java/Kotlin**: Never use for other languages\n2. **file:// for everything else**: C/C++, JavaScript, Python, Go, etc.\n3. **Visible text = exact symbol name**: `[Repository]`, `[handleSubmit]`, `[API_KEY]`\n4. **Use backticks when no context**: If you can't determine file location, use `backticks`\n5. **Methods must include owner**: `fully.qualified.ClassName#methodName` or `file.ext#functionName`\n6. **MANDATORY**: Always use fully qualified class names - never use short class names alone\n\n- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.\n- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.\n- For long `WebFetch` pages, paginate using `offset` and `limit`.\n- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.\n\nuser: Where are errors from the client handled?\nassistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Grep directly]\n\n\nuser: What is the codebase structure?\nassistant: [Uses the Task tool with subagent_type=Explore]\n\n\nYou can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, WebFetch, BashOutput, KillShell\n\nHere is useful information about the environment you are running in:\n\nWorking directory: /home/rizer/src/XChange\nToday's date: 2026-04-24\n\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\nIMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.\n\n# Code References\n\nWhen referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.\n\n\nuser: Where are errors from the client handled?\nassistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.\n" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190421016Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "# ProxyAI Instructions\n\nDescribe goals, conventions, risky areas, and review rules here." + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190426352Z", + "metadata": { + "cacheable": true + } + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:14.190983213Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414868569Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run geOrderBook() test in GateioFuturesManualExample" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T15:57:44.414880840Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java\nGateioFuturesManualExample\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150237068Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "test" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:11:43.150249934Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "```java:/home/rizer/src/XChange/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingServiceTest.java\nGateioStreamingServiceTest\n```" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:12:21.777645576Z" + } + }, + { + "type": "ai.koog.prompt.message.Message.User", + "parts": [ + { + "type": "text", + "text": "run GateioStreamingServiceTest" + } + ], + "metaInfo": { + "timestamp": "2026-04-24T16:12:21.777653718Z" + } + } + ], + "version": 3 +} \ No newline at end of file diff --git a/PROXYAI.md b/PROXYAI.md new file mode 100644 index 00000000000..88f3aa66709 --- /dev/null +++ b/PROXYAI.md @@ -0,0 +1,3 @@ +# ProxyAI Instructions + +Describe goals, conventions, risky areas, and review rules here. \ No newline at end of file diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/Gateio.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/Gateio.java index 431ecef56a4..398177e30aa 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/Gateio.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/Gateio.java @@ -1,20 +1,12 @@ package org.knowm.xchange.gateio; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; +import org.knowm.xchange.gateio.dto.GateioException; +import org.knowm.xchange.gateio.dto.marketdata.*; + import java.io.IOException; import java.util.List; -import org.knowm.xchange.gateio.dto.GateioException; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyChain; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyInfo; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyPairDetails; -import org.knowm.xchange.gateio.dto.marketdata.GateioOrderBook; -import org.knowm.xchange.gateio.dto.marketdata.GateioServerTime; -import org.knowm.xchange.gateio.dto.marketdata.GateioTicker; @Path("api/v4") @Produces(MediaType.APPLICATION_JSON) @@ -43,6 +35,10 @@ List getCurrencyChains(@QueryParam("currency") String curre @Path("spot/currency_pairs") List getCurrencyPairDetails() throws IOException, GateioException; + @GET + @Path("/futures/usdt/contracts") + List getInstrumentDetails() throws IOException, GateioException; + @GET @Path("spot/currency_pairs/{currency_pair}") GateioCurrencyPairDetails getCurrencyPairDetails(@PathParam("currency_pair") String currencyPair) @@ -52,4 +48,41 @@ GateioCurrencyPairDetails getCurrencyPairDetails(@PathParam("currency_pair") Str @Path("spot/tickers") List getTickers(@QueryParam("currency_pair") String currencyPair) throws IOException, GateioException; + + @GET + @Path("futures/{settle}/tickers") + List getFuturesTickers( + @PathParam("settle") String settle, @QueryParam("contract") String contract) + throws IOException, GateioException; + + @GET + @Path("spot/candlesticks") + List getSpotCandlesticks( + @QueryParam("currency_pair") String currencyPair, + @QueryParam("limit") Integer limit, + @QueryParam("from") Long from, + @QueryParam("to") Long to, + @QueryParam("interval") String interval) + throws IOException, GateioException; + + @GET + @Path("futures/{settle}/funding_rate") + List getFundingRateHistory( + @PathParam("settle") String settle, + @QueryParam("contract") String contract, + @QueryParam("limit") Integer limit, + @QueryParam("from") Long from, + @QueryParam("to") Long to) + throws IOException, GateioException; + + @GET + @Path("futures/{settle}/candlesticks") + List getFuturesCandlesticks( + @PathParam("settle") String settle, + @QueryParam("contract") String contract, + @QueryParam("limit") Integer limit, + @QueryParam("from") Long from, + @QueryParam("to") Long to, + @QueryParam("interval") String interval) + throws IOException, GateioException; } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioAdapters.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioAdapters.java index e96475c0394..0244fbd598f 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioAdapters.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioAdapters.java @@ -1,17 +1,14 @@ package org.knowm.xchange.gateio; -import java.math.BigDecimal; -import java.math.MathContext; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; import lombok.experimental.UtilityClass; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderStatus; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.account.FundingRecord; +import org.knowm.xchange.dto.marketdata.CandleStick; +import org.knowm.xchange.dto.marketdata.CandleStickData; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.meta.InstrumentMetaData; @@ -19,22 +16,28 @@ import org.knowm.xchange.dto.trade.MarketOrder; import org.knowm.xchange.dto.trade.UserTrade; import org.knowm.xchange.gateio.dto.account.GateioAccountBookRecord; -import org.knowm.xchange.gateio.dto.account.GateioOrder; import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRequest; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyPairDetails; -import org.knowm.xchange.gateio.dto.marketdata.GateioOrderBook; -import org.knowm.xchange.gateio.dto.marketdata.GateioTicker; -import org.knowm.xchange.gateio.dto.trade.GateioUserTrade; -import org.knowm.xchange.gateio.dto.trade.GateioUserTradeRaw; +import org.knowm.xchange.gateio.dto.marketdata.*; +import org.knowm.xchange.gateio.dto.trade.*; import org.knowm.xchange.gateio.service.params.GateioWithdrawFundsParams; import org.knowm.xchange.instrument.Instrument; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + @UtilityClass public class GateioAdapters { public final BigDecimal PARTIALLY_FILLED_SCALE = new BigDecimal("0.1"); - public String toString(Instrument instrument) { + public String toGateioInstrument(Instrument instrument) { if (instrument == null) { return null; } else { @@ -45,6 +48,21 @@ public String toString(Instrument instrument) { } } + public static Instrument fromGateioInstrument(String gateIoInstrument, boolean isFuture) { + String xchangeInstrument = gateIoInstrument.replace("_", "/"); + if (isFuture) { + if (!xchangeInstrument.contains("/")) { + // for futures, it might be just "BTC" or "BTC_USDT" + return new FuturesContract(xchangeInstrument + "/USDT/PERP"); + } + return new FuturesContract(new CurrencyPair(xchangeInstrument), "PERP"); + } + if (!xchangeInstrument.contains("/")) { + return null; + } + return new CurrencyPair(xchangeInstrument); + } + public OrderBook toOrderBook(GateioOrderBook gateioOrderBook, Instrument instrument) { List asks = gateioOrderBook.getAsks().stream() @@ -75,7 +93,7 @@ public OrderBook toOrderBook(GateioOrderBook gateioOrderBook, Instrument instrum return new OrderBook(Date.from(gateioOrderBook.getGeneratedAt()), asks, bids); } - public InstrumentMetaData toInstrumentMetaData( + public InstrumentMetaData currencyPairToInstrumentMetaData( GateioCurrencyPairDetails gateioCurrencyPairDetails) { return InstrumentMetaData.builder() .tradingFee(gateioCurrencyPairDetails.getFee()) @@ -86,7 +104,23 @@ public InstrumentMetaData toInstrumentMetaData( .build(); } - public String toString(OrderStatus orderStatus) { + public InstrumentMetaData instrumentToInstrumentMetaData( + GateioInstrumentDetails gateioInstrumentDetails) { + return InstrumentMetaData.builder() + .contractValue(gateioInstrumentDetails.getQuantoMultiplier()) + .tradingFee(gateioInstrumentDetails.getTakerFeeRate()) + .minimumAmount(gateioInstrumentDetails.getOrderSizeMin().multiply(gateioInstrumentDetails.getQuantoMultiplier()).stripTrailingZeros()) + .maximumAmount(gateioInstrumentDetails.getOrderSizeMax().multiply(gateioInstrumentDetails.getQuantoMultiplier()).stripTrailingZeros()) + .priceStepSize(gateioInstrumentDetails.getOrderPriceRound()) + // no data, so suggest that equals to order min size + .amountStepSize(gateioInstrumentDetails.getOrderSizeMin().multiply(gateioInstrumentDetails.getQuantoMultiplier()).stripTrailingZeros()) + .volumeScale(numberOfDecimals(gateioInstrumentDetails.getOrderSizeMin().multiply(gateioInstrumentDetails.getQuantoMultiplier()).stripTrailingZeros())) + .priceScale(numberOfDecimals(gateioInstrumentDetails.getOrderPriceRound())) + .contractValue(gateioInstrumentDetails.getQuantoMultiplier()) + .build(); + } + + public String toGateioInstrument(OrderStatus orderStatus) { switch (orderStatus) { case OPEN: return "open"; @@ -97,22 +131,21 @@ public String toString(OrderStatus orderStatus) { } } - public OrderStatus toOrderStatus(GateioOrder gateioOrder) { - switch (gateioOrder.getStatus()) { + public OrderStatus toOrderStatus(GateioSpotOrderResponse gateioSpotOrderResponse) { + switch (gateioSpotOrderResponse.getStatus()) { case "open": return OrderStatus.OPEN; case "closed": // if more than `PARTIALLY_FILLED_SCALE` left to fill -> set to `PARTIALLY_FILLED` - if (gateioOrder - .getAmountLeftToFill() - .compareTo(gateioOrder.getAmount().multiply(PARTIALLY_FILLED_SCALE)) + if (gateioSpotOrderResponse + .getAmountLeftToFill() + .compareTo(gateioSpotOrderResponse.getAmount().multiply(PARTIALLY_FILLED_SCALE)) > 0) { return OrderStatus.PARTIALLY_FILLED; } else { return OrderStatus.FILLED; } - case "filled": return OrderStatus.FILLED; @@ -121,36 +154,101 @@ public OrderStatus toOrderStatus(GateioOrder gateioOrder) { return OrderStatus.CANCELED; default: - throw new IllegalArgumentException("Can't map " + gateioOrder.getStatus()); + throw new IllegalArgumentException("Can't map " + gateioSpotOrderResponse.getStatus()); + } + } + + public OrderStatus toOrderStatusFutures(GateioFuturesOrderResponse gateioFuturesOrderResponse) { + + switch (gateioFuturesOrderResponse.getStatus()) { + case "open": + return OrderStatus.OPEN; + + case "finished": + switch (gateioFuturesOrderResponse.getFinishAs()) { + case "filled": + return OrderStatus.FILLED; + case "cancelled": + return OrderStatus.CANCELED; + } + + default: + throw new IllegalArgumentException("Can't map " + gateioFuturesOrderResponse.getStatus()); } } - public GateioOrder toGateioOrder(MarketOrder marketOrder) { - return GateioOrder.builder() - .currencyPair((CurrencyPair) marketOrder.getInstrument()) + public GateioSpotOrderRequest toGateioSpotOrderRequest(MarketOrder marketOrder) { + GateioSpotOrderRequest.GateioSpotOrderRequestBuilder builder = GateioSpotOrderRequest.builder() + .currencyPair(marketOrder.getInstrument()) .side(marketOrder.getType()) - .clientOrderId(marketOrder.getUserReference()) - .account("spot") + .clientOrderId(marketOrder.getUserReference() != null ? marketOrder.getUserReference() : null) .type("market") .timeInForce("ioc") - .amount(marketOrder.getOriginalAmount()) - .build(); + .amount(marketOrder.getOriginalAmount()); + builder.account("spot"); + return builder.build(); } - public GateioOrder toGateioOrder(LimitOrder limitOrder) { - return GateioOrder.builder() - .currencyPair((CurrencyPair) limitOrder.getInstrument()) + public GateioSpotOrderRequest toGateioSpotOrderRequest(LimitOrder limitOrder) { + GateioSpotOrderRequest.GateioSpotOrderRequestBuilder builder = GateioSpotOrderRequest.builder() + .currencyPair(limitOrder.getInstrument()) .side(limitOrder.getType()) - .clientOrderId(limitOrder.getUserReference()) - .account("spot") + .clientOrderId(limitOrder.getUserReference() != null ? limitOrder.getUserReference() : null) .type("limit") .timeInForce("gtc") .price(limitOrder.getLimitPrice()) - .amount(limitOrder.getOriginalAmount()) + .amount(limitOrder.getOriginalAmount()); + builder.account("spot"); + return builder.build(); + } + + public GateioFuturesOrderRequest toGateioFuturesOrder(MarketOrder marketOrder, BigDecimal contractValue) { + BigDecimal size = convertVolumeToContractSize(marketOrder.getOriginalAmount(), contractValue); + return GateioFuturesOrderRequest.builder() + .contract(toGateioInstrument(marketOrder.getInstrument())) + .size(marketOrder.getType() == OrderType.BID ? size : size.negate()) + .price(BigDecimal.ZERO) + .text(marketOrder.getUserReference() != null ? marketOrder.getUserReference() : null) + .timeInForce("ioc") // a price of 0 with tif as ioc represents a market order. .build(); } - public Order toOrder(GateioOrder gateioOrder) { + + public GateioFuturesOrderRequest toGateioFuturesOrder(LimitOrder limitOrder, BigDecimal contractValue) { + var builder = GateioFuturesOrderRequest.builder(); + if (limitOrder.getOrderFlags() != null) { + Set flags = limitOrder.getOrderFlags(); + for (var flag : flags) + if (flag instanceof GateioOrderFlags) + builder.timeInForce(((GateioOrderFlags) flag).timeInForce.name().toLowerCase()); + } + BigDecimal size = convertVolumeToContractSize(limitOrder.getOriginalAmount(), contractValue); + return builder.contract(toGateioInstrument(limitOrder.getInstrument())). + size(limitOrder.getType() == OrderType.BID ? size : size.negate()). + price(limitOrder.getLimitPrice()). + text(limitOrder.getUserReference() != null ? limitOrder.getUserReference() : null). + build(); + } + + + public Order toOrder(GateioFuturesOrderResponse gateioFutureOrderResponse) { + OrderType orderType = gateioFutureOrderResponse.getSize().signum() > 0 ? OrderType.BID : OrderType.ASK; + BigDecimal amount = gateioFutureOrderResponse.getSize().abs(); + + return new LimitOrder.Builder(orderType, fromGateioInstrument(gateioFutureOrderResponse.getContract(), true)) + .id(String.valueOf(gateioFutureOrderResponse.getId())) + .userReference(gateioFutureOrderResponse.getText()) + .originalAmount(amount) + .limitPrice(gateioFutureOrderResponse.getPrice()) + .cumulativeAmount(amount.subtract(gateioFutureOrderResponse.getLeft())) + .orderStatus(toOrderStatusFutures(gateioFutureOrderResponse)) + .timestamp(gateioFutureOrderResponse.getCreateTime() != null ? Date.from(gateioFutureOrderResponse.getCreateTime()) : null) + .averagePrice(gateioFutureOrderResponse.getAvgDealPrice()) + .build(); + } + + + public Order toOrder(GateioSpotOrderResponse gateioOrder) { Order.Builder builder; Instrument instrument = gateioOrder.getCurrencyPair(); OrderType orderType = gateioOrder.getSide(); @@ -221,9 +319,9 @@ public GateioWithdrawalRequest toGateioWithdrawalRequest(GateioWithdrawFundsPara .build(); } - public Ticker toTicker(GateioTicker gateioTicker) { + public Ticker toTickerSpot(GateioTicker gateioTicker) { return new Ticker.Builder() - .instrument(gateioTicker.getCurrencyPair()) + .instrument(fromGateioInstrument(gateioTicker.getCurrencyPair(), false)) .last(gateioTicker.getLastPrice()) .bid(gateioTicker.getHighestBid()) .bidSize(gateioTicker.getHighestBidSize()) @@ -237,6 +335,63 @@ public Ticker toTicker(GateioTicker gateioTicker) { .build(); } + public Ticker toTickerFutures(GateioFuturesTicker gateioTicker, BigDecimal contractValue) { + return new Ticker.Builder() + .instrument(fromGateioInstrument(gateioTicker.getContract(), true)) + .last(gateioTicker.getLastPrice()) + .bid(gateioTicker.getHighestBid()) + .bidSize(gateioTicker.getHighestBidSize()) + .ask(gateioTicker.getLowestAsk()) + .askSize(gateioTicker.getLowestAskSize()) + .high(gateioTicker.getHigh24h()) + .low(gateioTicker.getLow24h()) + .volume(convertContractSizeToVolume(gateioTicker.getVolume24h(), contractValue)) + .quoteVolume(gateioTicker.getVolume24hQuote()) + .percentageChange(gateioTicker.getChangePercentage24h()) + .build(); + } + + public CandleStickData toCandleStickDataSpot( + List gateioSpotCandlesticks, Instrument instrument) { + List candleSticks = + gateioSpotCandlesticks.stream() + .map( + gateioSpotCandlestick -> + new CandleStick.Builder() + .timestamp(Instant.ofEpochSecond(gateioSpotCandlestick.getTimestamp())) + .open(gateioSpotCandlestick.getOpen()) + .high(gateioSpotCandlestick.getHigh()) + .low(gateioSpotCandlestick.getLow()) + .close(gateioSpotCandlestick.getClose()) + .volume(gateioSpotCandlestick.getVolume()) + .quotaVolume(gateioSpotCandlestick.getQuoteVolume()) + .completed(gateioSpotCandlestick.isCompleted()) + .build()) + .collect(Collectors.toList()); + + return new CandleStickData(instrument, candleSticks); + } + + public CandleStickData toCandleStickDataFutures( + List gateioFuturesCandlesticks, Instrument instrument, BigDecimal contractValue) { + List candleSticks = + gateioFuturesCandlesticks.stream() + .map( + gateioFuturesCandlestick -> + new CandleStick.Builder() + .timestamp(Instant.ofEpochSecond(gateioFuturesCandlestick.getTimestamp())) + .open(gateioFuturesCandlestick.getOpen()) + .high(gateioFuturesCandlestick.getHigh()) + .low(gateioFuturesCandlestick.getLow()) + .close(gateioFuturesCandlestick.getClose()) + .volume(convertContractSizeToVolume(gateioFuturesCandlestick.getVolume(), contractValue)) + .quotaVolume(gateioFuturesCandlestick.getQuoteVolume()) + .build()) + .collect(Collectors.toList()); + + return new CandleStickData(instrument, candleSticks); + } + public FundingRecord toFundingRecords(GateioAccountBookRecord gateioAccountBookRecord) { return FundingRecord.builder() .internalId(gateioAccountBookRecord.getId()) @@ -248,4 +403,21 @@ public FundingRecord toFundingRecords(GateioAccountBookRecord gateioAccountBookR .description(gateioAccountBookRecord.getTypeDescription()) .build(); } + + private static int numberOfDecimals(BigDecimal value) { + double d = value.doubleValue(); + return -(int) Math.round(Math.log10(d)); + } + + private static BigDecimal convertContractSizeToVolume( + BigDecimal size, BigDecimal contractValue) { + return size.multiply(contractValue).stripTrailingZeros(); + } + + private static BigDecimal convertVolumeToContractSize( + BigDecimal size, BigDecimal contractValue) { + return size.divide(contractValue, 20, RoundingMode.HALF_DOWN) + .stripTrailingZeros(); + } + } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioExchange.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioExchange.java index dd0fa22eb18..5952697c5e3 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioExchange.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioExchange.java @@ -1,12 +1,11 @@ package org.knowm.xchange.gateio; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.TimeUnit; import org.knowm.xchange.BaseExchange; import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.dto.meta.ExchangeMetaData; import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.gateio.dto.GateioExchangeType; import org.knowm.xchange.gateio.service.GateioAccountService; import org.knowm.xchange.gateio.service.GateioMarketDataService; import org.knowm.xchange.gateio.service.GateioTradeService; @@ -14,22 +13,30 @@ import org.knowm.xchange.utils.nonce.CurrentTimeIncrementalNonceFactory; import si.mazi.rescu.SynchronizedValueFactory; -public class GateioExchange extends BaseExchange { +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import static org.knowm.xchange.gateio.dto.GateioExchangeType.SPOT; + +public class GateioExchange extends BaseExchange { + public static String EXCHANGE_TYPE = "Exchange_Type"; + private static ResilienceRegistries RESILIENCE_REGISTRIES; private final SynchronizedValueFactory nonceFactory = new CurrentTimeIncrementalNonceFactory(TimeUnit.SECONDS); @Override protected void initServices() { - marketDataService = new GateioMarketDataService(this); - accountService = new GateioAccountService(this); - tradeService = new GateioTradeService(this); + marketDataService = new GateioMarketDataService(this, getResilienceRegistries()); + accountService = new GateioAccountService(this, getResilienceRegistries()); + tradeService = new GateioTradeService(this, getResilienceRegistries()); } @Override public ExchangeSpecification getDefaultExchangeSpecification() { ExchangeSpecification specification = new ExchangeSpecification(this.getClass()); + specification.setExchangeSpecificParametersItem(EXCHANGE_TYPE, SPOT); specification.setSslUri("https://api.gateio.ws"); specification.setHost("gate.io"); specification.setExchangeName("Gateio"); @@ -49,4 +56,17 @@ public void remoteInit() throws IOException { exchangeMetaData = new ExchangeMetaData(instruments, null, null, null, null); } + + public boolean isFuturesEnabled() { + return GateioExchangeType.FUTURES.equals( + exchangeSpecification.getExchangeSpecificParametersItem(EXCHANGE_TYPE)); + } + + @Override + public ResilienceRegistries getResilienceRegistries() { + if (RESILIENCE_REGISTRIES == null) { + RESILIENCE_REGISTRIES = GateioResilience.createRegistries(isFuturesEnabled()); + } + return RESILIENCE_REGISTRIES; + } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioResilience.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioResilience.java new file mode 100644 index 00000000000..f9f216c5895 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioResilience.java @@ -0,0 +1,86 @@ +package org.knowm.xchange.gateio; + +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import org.knowm.xchange.client.ResilienceRegistries; + +import java.time.Duration; + +public class GateioResilience { + public static final String ORDERS_RATE_LIMITER = "ordersPerSecond"; + public static final String DYNAMIC_TRADING_FEE_RATE_LIMITER = "dynamicTradingFee"; + public static final String LEVERAGE_RATE_LIMITER = "setLeverage"; + public static final String TICKERS_RATE_LIMITER = "tickers"; + public static final String FUNDING_HISTORY_RATE_LIMITER = "fundingHistory"; + public static final String CANDLESTICK_DATA_RATE_LIMITER = "candlestickData"; + + public static ResilienceRegistries createRegistries(boolean isFutures) { + ResilienceRegistries registries = new ResilienceRegistries(); + registries + .rateLimiters() + .rateLimiter( + DYNAMIC_TRADING_FEE_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(10)) + .limitForPeriod(200) + .timeoutDuration(Duration.ofSeconds(0)) + .build()); + registries + .rateLimiters() + .rateLimiter( + LEVERAGE_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(10)) + .timeoutDuration(Duration.ofSeconds(0)) + .limitForPeriod(200) + .build()); + registries + .rateLimiters() + .rateLimiter( + FUNDING_HISTORY_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(10)) + .limitForPeriod(200) + .timeoutDuration(Duration.ofSeconds(0)) + .build()); + registries + .rateLimiters() + .rateLimiter( + CANDLESTICK_DATA_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(10)) + .timeoutDuration(Duration.ofSeconds(0)) + .limitForPeriod(200) + .build()); + registries + .rateLimiters() + .rateLimiter( + TICKERS_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(10)) + .timeoutDuration(Duration.ofSeconds(0)) + .limitForPeriod(200) + .build()); + if (isFutures) + registries + .rateLimiters() + .rateLimiter( + ORDERS_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .timeoutDuration(Duration.ofSeconds(0)) + .limitForPeriod(100) + .build()); + else + registries + .rateLimiters() + .rateLimiter( + ORDERS_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .timeoutDuration(Duration.ofSeconds(0)) + .limitForPeriod(10) + .build()); + + return registries; + } +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioV4Authenticated.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioV4Authenticated.java index 77f5603e4a3..40ac0d311be 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioV4Authenticated.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/GateioV4Authenticated.java @@ -1,32 +1,21 @@ package org.knowm.xchange.gateio; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.HeaderParam; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; -import java.io.IOException; -import java.util.List; import org.knowm.xchange.gateio.dto.GateioException; -import org.knowm.xchange.gateio.dto.account.GateioAccountBookRecord; -import org.knowm.xchange.gateio.dto.account.GateioAddressRecord; -import org.knowm.xchange.gateio.dto.account.GateioCurrencyBalance; -import org.knowm.xchange.gateio.dto.account.GateioDepositAddress; -import org.knowm.xchange.gateio.dto.account.GateioDepositRecord; -import org.knowm.xchange.gateio.dto.account.GateioOrder; -import org.knowm.xchange.gateio.dto.account.GateioSubAccountTransfer; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawStatus; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRecord; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRequest; +import org.knowm.xchange.gateio.dto.account.*; +import org.knowm.xchange.gateio.dto.trade.GateioFuturesOrderResponse; +import org.knowm.xchange.gateio.dto.trade.GateioFuturesOrderRequest; +import org.knowm.xchange.gateio.dto.trade.GateioSpotOrderRequest; +import org.knowm.xchange.gateio.dto.trade.GateioSpotOrderResponse; import org.knowm.xchange.gateio.dto.trade.GateioUserTradeRaw; import si.mazi.rescu.ParamsDigest; import si.mazi.rescu.SynchronizedValueFactory; +import java.io.IOException; +import java.util.List; +import java.util.Map; + @Path("api/v4") @Produces(MediaType.APPLICATION_JSON) public interface GateioV4Authenticated { @@ -58,6 +47,25 @@ List getSpotAccounts( @QueryParam("currency") String currency) throws IOException, GateioException; + @GET + @Path("/wallet/fee") + GateioSpotFee getSpotFee( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @QueryParam("currency_pair") String currencyPair) + throws IOException, GateioException; + + @GET + @Path("futures/{settle}/fee") + Map getFuturesFee( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @PathParam("settle") String settle, + @QueryParam("contract") String contract) + throws IOException, GateioException; + @GET @Path("spot/account_book") List getAccountBookRecords( @@ -74,7 +82,7 @@ List getAccountBookRecords( @GET @Path("spot/orders") - List listOrders( + List listOrders( @HeaderParam("KEY") String apiKey, @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, @HeaderParam("SIGN") ParamsDigest signer, @@ -84,7 +92,7 @@ List listOrders( @GET @Path("spot/orders/{order_id}") - GateioOrder getOrder( + GateioSpotOrderResponse getOrder( @HeaderParam("KEY") String apiKey, @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, @HeaderParam("SIGN") ParamsDigest signer, @@ -92,9 +100,20 @@ GateioOrder getOrder( @QueryParam("currency_pair") String currencyPair) throws IOException, GateioException; + @GET + @Path("futures/{settle}/orders/{order_id}") + GateioFuturesOrderResponse getFuturesOrder( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @HeaderParam("x-gate-exptime") Long expirationTime, + @PathParam("settle") String settle, + @PathParam("order_id") String orderId) + throws IOException, GateioException; + @DELETE @Path("spot/orders/{order_id}") - GateioOrder cancelOrder( + GateioSpotOrderResponse cancelOrder( @HeaderParam("KEY") String apiKey, @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, @HeaderParam("SIGN") ParamsDigest signer, @@ -102,14 +121,62 @@ GateioOrder cancelOrder( @QueryParam("currency_pair") String currencyPair) throws IOException, GateioException; + @DELETE + @Path("futures/{settle}/orders/{order_id}") + GateioFuturesOrderResponse cancelFuturesOrder( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @HeaderParam("x-gate-exptime") Long expirationTime, + @PathParam("settle") String settle, + @PathParam("order_id") String orderId) + throws IOException, GateioException; + + @PATCH + @Path("spot/orders/{order_id}") + @Consumes(MediaType.APPLICATION_JSON) + GateioSpotOrderResponse amendOrder( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @PathParam("order_id") String orderId, + @QueryParam("currency_pair") String currencyPair, + Map request) + throws IOException, GateioException; + + @PUT + @Path("futures/{settle}/orders/{order_id}") + @Consumes(MediaType.APPLICATION_JSON) + GateioFuturesOrderResponse amendFuturesOrder( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @HeaderParam("x-gate-exptime") Long expirationTime, + @PathParam("settle") String settle, + @PathParam("order_id") String orderId, + Map request) + throws IOException, GateioException; + @POST @Path("spot/orders") @Consumes(MediaType.APPLICATION_JSON) - GateioOrder createOrder( + GateioSpotOrderResponse createOrder( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + GateioSpotOrderRequest gateioOrder) + throws IOException, GateioException; + + @POST + @Path("futures/{settle}/orders") + @Consumes(MediaType.APPLICATION_JSON) + GateioFuturesOrderResponse createFuturesOrder( @HeaderParam("KEY") String apiKey, @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, @HeaderParam("SIGN") ParamsDigest signer, - GateioOrder gateioOrder) + @HeaderParam("x-gate-exptime") Long expirationTime, + @PathParam("settle") String settle, + GateioFuturesOrderRequest gateioFuturesOrder) throws IOException, GateioException; @GET @@ -184,4 +251,16 @@ GateioWithdrawalRecord withdraw( @HeaderParam("SIGN") ParamsDigest signer, GateioWithdrawalRequest gateioWithdrawalRequest) throws IOException, GateioException; + + @POST + @Path("futures/{settle}/positions/{contract}/leverage") + @Consumes(MediaType.APPLICATION_JSON) + GateioPositionLeverageUpdate updatePositionLeverage( + @HeaderParam("KEY") String apiKey, + @HeaderParam("Timestamp") SynchronizedValueFactory timestamp, + @HeaderParam("SIGN") ParamsDigest signer, + @PathParam("settle") String settle, + @PathParam("contract") String contract, + @QueryParam("leverage") String leverage) + throws IOException, GateioException; } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/CurrencyPairToStringConverter.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/CurrencyPairToStringConverter.java index 42e42007b71..e899d5dbb12 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/CurrencyPairToStringConverter.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/CurrencyPairToStringConverter.java @@ -1,13 +1,13 @@ package org.knowm.xchange.gateio.config.converter; import com.fasterxml.jackson.databind.util.StdConverter; -import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.instrument.Instrument; /** Converts {@code CurrencyPair} to string */ -public class CurrencyPairToStringConverter extends StdConverter { +public class CurrencyPairToStringConverter extends StdConverter { @Override - public String convert(CurrencyPair value) { + public String convert(Instrument value) { return value.getBase() + "_" + value.getCounter(); } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/InstrumentToStringConverter.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/InstrumentToStringConverter.java new file mode 100644 index 00000000000..55c06d11370 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/InstrumentToStringConverter.java @@ -0,0 +1,11 @@ +package org.knowm.xchange.gateio.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import org.knowm.xchange.instrument.Instrument; + +public class InstrumentToStringConverter extends StdConverter { + @Override + public String convert(Instrument value) { + return value.getBase() + "_" + value.getCounter(); + } +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/StringToCurrencyPairConverter.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/StringToCurrencyPairConverter.java index 8d5a000111c..9da873a632d 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/StringToCurrencyPairConverter.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/config/converter/StringToCurrencyPairConverter.java @@ -2,12 +2,15 @@ import com.fasterxml.jackson.databind.util.StdConverter; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.instrument.Instrument; -/** Converts string to {@code CurrencyPair} */ -public class StringToCurrencyPairConverter extends StdConverter { +/** + * Converts string to {@code Instrument} + */ +public class StringToCurrencyPairConverter extends StdConverter { @Override - public CurrencyPair convert(String value) { + public Instrument convert(String value) { return new CurrencyPair(value.replace('_', '/')); } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/GateioExchangeType.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/GateioExchangeType.java new file mode 100644 index 00000000000..1e84de84925 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/GateioExchangeType.java @@ -0,0 +1,6 @@ +package org.knowm.xchange.gateio.dto; + +public enum GateioExchangeType { + SPOT, + FUTURES, +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioFuturesFee.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioFuturesFee.java new file mode 100644 index 00000000000..cba02851586 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioFuturesFee.java @@ -0,0 +1,16 @@ +package org.knowm.xchange.gateio.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; + +import lombok.Data; + +@Data +public class GateioFuturesFee { + @JsonProperty("taker_fee") + BigDecimal takerFee; + + @JsonProperty("maker_fee") + BigDecimal makerFee; +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioPositionLeverageUpdate.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioPositionLeverageUpdate.java new file mode 100644 index 00000000000..e137998846e --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioPositionLeverageUpdate.java @@ -0,0 +1,24 @@ +package org.knowm.xchange.gateio.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.math.BigDecimal; + +@Getter +public class GateioPositionLeverageUpdate { + + @JsonProperty("leverage") + private final BigDecimal leverage; + + @JsonProperty("cross_leverage_limit") + private final BigDecimal crossLeverageLimit; + + public GateioPositionLeverageUpdate( + @JsonProperty("leverage") BigDecimal leverage, + @JsonProperty("cross_leverage_limit") BigDecimal crossLeverageLimit) { + this.leverage = leverage; + this.crossLeverageLimit = crossLeverageLimit; + } + +} \ No newline at end of file diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioSpotFee.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioSpotFee.java new file mode 100644 index 00000000000..712d0e79b49 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioSpotFee.java @@ -0,0 +1,60 @@ +package org.knowm.xchange.gateio.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; + +import lombok.Data; + +@Data +public class GateioSpotFee { + + @JsonProperty("user_id") + Long userId; + + @JsonProperty("taker_fee") + BigDecimal takerFee; + + @JsonProperty("maker_fee") + BigDecimal makerFee; + + @JsonProperty("gt_discount") + Boolean gtDiscount; + + @JsonProperty("gt_taker_fee") + BigDecimal gtTakerFee; + + @JsonProperty("gt_maker_fee") + BigDecimal gtMakerFee; + + @JsonProperty("loan_fee") + BigDecimal loanFee; + + @JsonProperty("point_type") + String pointType; + + @JsonProperty("futures_taker_fee") + BigDecimal futuresTakerFee; + + @JsonProperty("futures_maker_fee") + BigDecimal futuresMakerFee; + + @JsonProperty("delivery_taker_fee") + BigDecimal deliveryTakerFee; + + @JsonProperty("delivery_maker_fee") + BigDecimal deliveryMakerFee; + + @JsonProperty("debit_fee") + Integer debitFee; + + @JsonProperty("rpi_maker_fee") + BigDecimal rpiMakerFee; + + @JsonProperty("futures_rpi_maker_fee") + BigDecimal futuresRpiMakerFee; + + @JsonProperty("rpi_mm") + Integer rpiMm; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFundingRateHistory.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFundingRateHistory.java new file mode 100644 index 00000000000..9db4705f697 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFundingRateHistory.java @@ -0,0 +1,32 @@ +package org.knowm.xchange.gateio.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class GateioFundingRateHistory { + + private final long timestamp; + private final String rate; + + public GateioFundingRateHistory( + @JsonProperty("t") long timestamp, + @JsonProperty("r") String rate) { + this.timestamp = timestamp; + this.rate = rate; + } + + public long getTimestamp() { + return timestamp; + } + + public String getRate() { + return rate; + } + + @Override + public String toString() { + return "GateioFundingRateHistory{" + + "timestamp=" + timestamp + + ", rate='" + rate + '\'' + + '}'; + } +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesCandlestick.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesCandlestick.java new file mode 100644 index 00000000000..cfb14ffa50f --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesCandlestick.java @@ -0,0 +1,33 @@ +package org.knowm.xchange.gateio.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class GateioFuturesCandlestick { + + @JsonProperty("t") + // in seconds + private long timestamp; + // size volume (contract size). Only returned if contract is not prefixed + @JsonProperty("v") + private BigDecimal volume; + + @JsonProperty("c") + private BigDecimal close; + + @JsonProperty("h") + private BigDecimal high; + + @JsonProperty("l") + private BigDecimal low; + + @JsonProperty("o") + private BigDecimal open; + // Trading volume (unit: Quote currency) + @JsonProperty("sum") + private BigDecimal quoteVolume; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesTicker.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesTicker.java new file mode 100644 index 00000000000..ca84e4bb87e --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesTicker.java @@ -0,0 +1,85 @@ +package org.knowm.xchange.gateio.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class GateioFuturesTicker { + + @JsonProperty("contract") + String contract; + + @JsonProperty("last") + BigDecimal lastPrice; + + @JsonProperty("change_percentage") + BigDecimal changePercentage24h; + + @JsonProperty("total_size") + BigDecimal totalSize; + + @JsonProperty("volume_24h") + BigDecimal volume24h; + + @JsonProperty("volume_24h_btc") + BigDecimal volume24hBtc; + + @JsonProperty("volume_24h_usd") + BigDecimal volume24hUsd; + + @JsonProperty("volume_24h_base") + BigDecimal volume24hBase; + + @JsonProperty("volume_24h_quote") + BigDecimal volume24hQuote; + + @JsonProperty("volume_24h_settle") + BigDecimal volume24hSettle; + + @JsonProperty("mark_price") + BigDecimal markPrice; + + @JsonProperty("funding_rate") + BigDecimal fundingRate; + + @JsonProperty("funding_rate_indicative") + BigDecimal fundingRateIndicative; + + @JsonProperty("index_price") + BigDecimal indexPrice; + + @JsonProperty("lowest_ask") + BigDecimal lowestAsk; + + @JsonProperty("highest_bid") + BigDecimal highestBid; + + @JsonProperty("lowest_size") + BigDecimal lowestAskSize; + + @JsonProperty("highest_size") + BigDecimal highestBidSize; + + @JsonProperty("low_24h") + BigDecimal low24h; + + @JsonProperty("high_24h") + BigDecimal high24h; + + @JsonProperty("change_utc0") + BigDecimal changeUtc0; + + @JsonProperty("change_utc8") + BigDecimal changeUtc8; + + @JsonProperty("quanto_base_rate") + BigDecimal quantoBaseRate; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioInstrumentDetails.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioInstrumentDetails.java new file mode 100644 index 00000000000..2c1df3c0e5c --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioInstrumentDetails.java @@ -0,0 +1,165 @@ +package org.knowm.xchange.gateio.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.gateio.config.converter.DoubleToInstantConverter; + +import java.math.BigDecimal; +import java.time.Instant; + +@Data +@Builder +@Jacksonized +public class GateioInstrumentDetails { + + @JsonProperty("name") + String name; + + @JsonProperty("type") + String type; + + @JsonProperty("quanto_multiplier") + BigDecimal quantoMultiplier; + + @JsonProperty("leverage_min") + BigDecimal leverageMin; + + @JsonProperty("leverage_max") + BigDecimal leverageMax; + + @JsonProperty("maintenance_rate") + BigDecimal maintenanceRate; + + @JsonProperty("mark_type") + String markType; + + @JsonProperty("mark_price") + BigDecimal markPrice; + + @JsonProperty("index_price") + BigDecimal indexPrice; + + @JsonProperty("last_price") + BigDecimal lastPrice; + + @JsonProperty("maker_fee_rate") + BigDecimal makerFeeRate; + + @JsonProperty("taker_fee_rate") + BigDecimal takerFeeRate; + + @JsonProperty("order_price_round") + BigDecimal orderPriceRound; + + @JsonProperty("mark_price_round") + BigDecimal markPriceRound; + + @JsonProperty("funding_rate") + BigDecimal fundingRate; + + @JsonProperty("funding_interval") + Integer fundingInterval; + + @JsonProperty("funding_next_apply") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant fundingNextApply; + + @JsonProperty("risk_limit_base") + BigDecimal riskLimitBase; + + @JsonProperty("interest_rate") + BigDecimal interestRate; + + @JsonProperty("risk_limit_step") + BigDecimal riskLimitStep; + + @JsonProperty("risk_limit_max") + BigDecimal riskLimitMax; + + @JsonProperty("order_size_min") + BigDecimal orderSizeMin; + + @JsonProperty("enable_decimal") + Boolean enableDecimal; + + @JsonProperty("order_size_max") + BigDecimal orderSizeMax; + + @JsonProperty("order_price_deviate") + BigDecimal orderPriceDeviate; + + @JsonProperty("ref_discount_rate") + BigDecimal refDiscountRate; + + @JsonProperty("ref_rebate_rate") + BigDecimal refRebateRate; + + @JsonProperty("orderbook_id") + Long orderbookId; + + @JsonProperty("trade_id") + Long tradeId; + + @JsonProperty("trade_size") + BigDecimal tradeSize; + + @JsonProperty("position_size") + BigDecimal positionSize; + + @JsonProperty("config_change_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant configChangeTime; + + @JsonProperty("in_delisting") + Boolean inDelisting; + + @JsonProperty("orders_limit") + Integer ordersLimit; + + @JsonProperty("enable_bonus") + Boolean enableBonus; + + @JsonProperty("enable_credit") + Boolean enableCredit; + + @JsonProperty("create_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant createTime; + + @JsonProperty("funding_cap_ratio") + BigDecimal fundingCapRatio; + + @JsonProperty("status") + String status; + + @JsonProperty("launch_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant launchTime; + + @JsonProperty("delisting_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant delistingTime; + + @JsonProperty("delisted_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant delistedTime; + + @JsonProperty("market_order_slip_ratio") + BigDecimal marketOrderSlipRatio; + + @JsonProperty("market_order_size_max") + BigDecimal marketOrderSizeMax; + + @JsonProperty("funding_rate_limit") + BigDecimal fundingRateLimit; + + @JsonProperty("contract_type") + String contractType; + + @JsonProperty("funding_impact_value") + BigDecimal fundingImpactValue; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioSpotCandlestick.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioSpotCandlestick.java new file mode 100644 index 00000000000..8968a8adc4c --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioSpotCandlestick.java @@ -0,0 +1,39 @@ +package org.knowm.xchange.gateio.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@JsonFormat(shape = JsonFormat.Shape.ARRAY) +@JsonPropertyOrder({"timestamp", "quoteVolume", "close", "high", "low", "open", "volume", "completed"}) +public class GateioSpotCandlestick { + + @JsonProperty("timestamp") + // in seconds + private long timestamp; + //Trading volume in quote currency + @JsonProperty("volume") + private BigDecimal volume; + + @JsonProperty("close") + private BigDecimal close; + + @JsonProperty("high") + private BigDecimal high; + + @JsonProperty("low") + private BigDecimal low; + + @JsonProperty("open") + private BigDecimal open; + //Trading volume in base currency + @JsonProperty("quoteVolume") + private BigDecimal quoteVolume; + //Whether window is closed; true means this candlestick data segment is complete, false means not yet complete + @JsonProperty("finished") + private boolean completed; +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioTicker.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioTicker.java index 72c2bcbdf01..08cca1855f7 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioTicker.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/marketdata/GateioTicker.java @@ -16,8 +16,8 @@ public class GateioTicker { @JsonProperty("currency_pair") - @JsonDeserialize(converter = StringToCurrencyPairConverter.class) - CurrencyPair currencyPair; +// @JsonDeserialize(converter = StringToCurrencyPairConverter.class) + String currencyPair; @JsonProperty("last") BigDecimal lastPrice; diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioCancelOrderParams.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioCancelOrderParams.java new file mode 100644 index 00000000000..28b96c20be5 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioCancelOrderParams.java @@ -0,0 +1,20 @@ +package org.knowm.xchange.gateio.dto.trade; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.service.trade.params.CancelOrderByIdParams; +import org.knowm.xchange.service.trade.params.CancelOrderByInstrument; +import org.knowm.xchange.service.trade.params.CancelOrderByUserReferenceParams; + +@AllArgsConstructor +@ToString +@Getter +public class GateioCancelOrderParams + implements CancelOrderByIdParams, CancelOrderByInstrument, CancelOrderByUserReferenceParams { + private final String orderId; + private final Instrument instrument; + private final String userReference; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioFuturesOrderRequest.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioFuturesOrderRequest.java new file mode 100644 index 00000000000..5a755778b14 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioFuturesOrderRequest.java @@ -0,0 +1,56 @@ +package org.knowm.xchange.gateio.dto.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + + +@Data +@Builder +@Jacksonized +public class GateioFuturesOrderRequest { + + @JsonProperty("contract") + String contract; + + @JsonProperty("size") + BigDecimal size; + + @JsonProperty("iceberg") + BigDecimal iceberg; + + @JsonProperty("price") + BigDecimal price; + + @JsonProperty("close") + Boolean close; + + @JsonProperty("reduce_only") + Boolean reduceOnly; + + @JsonProperty("tif") + String timeInForce; + + @JsonProperty("text") + String text; + + @JsonProperty("auto_size") + String autoSize; + + @JsonProperty("stp_act") + String stpAct; + + @JsonProperty("pid") + Long pid; + + @JsonProperty("market_order_slip_ratio") + BigDecimal marketOrderSlipRatio; + + @JsonProperty("pos_margin_mode") + String posMarginMode; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioFuturesOrderResponse.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioFuturesOrderResponse.java new file mode 100644 index 00000000000..35b39631270 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioFuturesOrderResponse.java @@ -0,0 +1,109 @@ +package org.knowm.xchange.gateio.dto.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.gateio.config.converter.DoubleToInstantConverter; +import org.knowm.xchange.gateio.config.converter.StringToCurrencyPairConverter; + +import java.math.BigDecimal; +import java.time.Instant; + +@Data +@Builder +@Jacksonized +public class GateioFuturesOrderResponse { + + @JsonProperty("id") + Long id; + + @JsonProperty("create_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant createTime; + + @JsonProperty("finish_time") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant finishTime; + + @JsonProperty("finish_as") + String finishAs; + + @JsonProperty("status") + String status; + + @JsonProperty("contract") + @JsonDeserialize(converter = StringToCurrencyPairConverter.class) + String contract; + + @JsonProperty("size") + BigDecimal size; + + @JsonProperty("iceberg") + BigDecimal iceberg; + + @JsonProperty("price") + BigDecimal price; + + @JsonProperty("close") + Boolean close; + + @JsonProperty("is_close") + Boolean isClose; + + @JsonProperty("reduce_only") + Boolean reduceOnly; + + @JsonProperty("is_reduce_only") + Boolean isReduceOnly; + + @JsonProperty("type") + String type; + + @JsonProperty("tif") + String timeInForce; + + @JsonProperty("left") + BigDecimal left; + + @JsonProperty("filled_total") + BigDecimal filledTotal; + + @JsonProperty("avg_deal_price") + BigDecimal avgDealPrice; + + @JsonProperty("fill_price") + BigDecimal fillPrice; + + @JsonProperty("text") + String text; + + @JsonProperty("user") + String user; + + @JsonProperty("tkfr") + BigDecimal takerFeeRate; + + @JsonProperty("mkfr") + BigDecimal makerFeeRate; + + @JsonProperty("refu") + BigDecimal referralRebate; + + @JsonProperty("auto_size") + String autoSize; + + @JsonProperty("stp_id") + Long stpId; + + @JsonProperty("stp_act") + String stpAct; + + @JsonProperty("amend_text") + String amendText; + + @JsonProperty("biz_info") + String bizInfo; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioOrderFlags.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioOrderFlags.java new file mode 100644 index 00000000000..e5cb2409f6e --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioOrderFlags.java @@ -0,0 +1,10 @@ +package org.knowm.xchange.gateio.dto.trade; + +import lombok.AllArgsConstructor; +import org.knowm.xchange.dto.Order; + +@AllArgsConstructor +public class GateioOrderFlags implements Order.IOrderFlags { + public GateioTimeInForce timeInForce; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioSpotOrderRequest.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioSpotOrderRequest.java new file mode 100644 index 00000000000..9ca6fab0728 --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioSpotOrderRequest.java @@ -0,0 +1,59 @@ +package org.knowm.xchange.gateio.dto.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.math.BigDecimal; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.gateio.config.converter.CurrencyPairToStringConverter; +import org.knowm.xchange.gateio.config.converter.OrderTypeToStringConverter; +import org.knowm.xchange.instrument.Instrument; + +@Data +@Builder +@Jacksonized +public class GateioSpotOrderRequest { + + @JsonProperty("text") + private String clientOrderId; + + @JsonProperty("currency_pair") + @JsonSerialize(converter = CurrencyPairToStringConverter.class) + private Instrument currencyPair; + + @JsonProperty("type") + private String type; + + @JsonProperty("account") + private String account; + + @JsonProperty("side") + @JsonSerialize(converter = OrderTypeToStringConverter.class) + private OrderType side; + + @JsonProperty("amount") + private BigDecimal amount; + + @JsonProperty("price") + private BigDecimal price; + + @JsonProperty("time_in_force") + private String timeInForce; + + @JsonProperty("iceberg") + private BigDecimal icebergAmount; + + @JsonProperty("auto_borrow") + private Boolean autoBorrow; + + @JsonProperty("auto_repay") + private Boolean autoRepay; + + @JsonProperty("stp_act") + private String stpAction; + +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioOrder.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioSpotOrderResponse.java similarity index 58% rename from xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioOrder.java rename to xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioSpotOrderResponse.java index e5e228add2b..12c952ac04e 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/account/GateioOrder.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioSpotOrderResponse.java @@ -1,123 +1,118 @@ -package org.knowm.xchange.gateio.dto.account; +package org.knowm.xchange.gateio.dto.trade; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; + import java.math.BigDecimal; import java.time.Instant; + import lombok.Builder; import lombok.Data; import lombok.extern.jackson.Jacksonized; import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order.OrderType; -import org.knowm.xchange.gateio.config.converter.CurrencyPairToStringConverter; -import org.knowm.xchange.gateio.config.converter.OrderTypeToStringConverter; +import org.knowm.xchange.gateio.config.converter.DoubleToInstantConverter; import org.knowm.xchange.gateio.config.converter.StringToCurrencyConverter; import org.knowm.xchange.gateio.config.converter.StringToCurrencyPairConverter; import org.knowm.xchange.gateio.config.converter.StringToOrderTypeConverter; +import org.knowm.xchange.instrument.Instrument; @Data @Builder @Jacksonized -public class GateioOrder { +public class GateioSpotOrderResponse { @JsonProperty("id") - String id; + private String id; @JsonProperty("text") - String clientOrderId; + private String clientOrderId; @JsonProperty("amend_text") - String amendText; + private String amendText; @JsonProperty("create_time_ms") - Instant createdAt; + @JsonDeserialize(converter = DoubleToInstantConverter.class) + private Instant createdAt; @JsonProperty("update_time_ms") - Instant updatedAt; + @JsonDeserialize(converter = DoubleToInstantConverter.class) + private Instant updatedAt; @JsonProperty("status") - String status; + private String status; @JsonProperty("currency_pair") @JsonDeserialize(converter = StringToCurrencyPairConverter.class) - @JsonSerialize(converter = CurrencyPairToStringConverter.class) - CurrencyPair currencyPair; + private Instrument currencyPair; @JsonProperty("type") - String type; + private String type; @JsonProperty("account") - String account; + private String account; @JsonProperty("side") @JsonDeserialize(converter = StringToOrderTypeConverter.class) - @JsonSerialize(converter = OrderTypeToStringConverter.class) - OrderType side; + private OrderType side; @JsonProperty("amount") - BigDecimal amount; + private BigDecimal amount; @JsonProperty("price") - BigDecimal price; + private BigDecimal price; @JsonProperty("time_in_force") - String timeInForce; + private String timeInForce; @JsonProperty("iceberg") - BigDecimal icebergAmount; - - @JsonProperty("auto_borrow") - Boolean autoBorrow; - - @JsonProperty("auto_repay") - Boolean autoRepay; + private BigDecimal icebergAmount; @JsonProperty("left") - BigDecimal amountLeftToFill; + private BigDecimal amountLeftToFill; @JsonProperty("filled_total") - BigDecimal filledTotalQuote; + private BigDecimal filledTotalQuote; @JsonProperty("avg_deal_price") - BigDecimal avgDealPrice; + private BigDecimal avgDealPrice; @JsonProperty("fee") - BigDecimal fee; + private BigDecimal fee; @JsonProperty("fee_currency") @JsonDeserialize(converter = StringToCurrencyConverter.class) - Currency feeCurrency; + private Currency feeCurrency; @JsonProperty("point_fee") - BigDecimal pointFee; + private BigDecimal pointFee; @JsonProperty("gt_fee") - BigDecimal gtFee; + private BigDecimal gtFee; @JsonProperty("gt_maker_fee") - BigDecimal gtMakerFee; + private BigDecimal gtMakerFee; @JsonProperty("gt_taker_fee") - BigDecimal gtTakerFee; + private BigDecimal gtTakerFee; @JsonProperty("gt_discount") - Boolean gtDiscount; + private Boolean gtDiscount; @JsonProperty("rebated_fee") - BigDecimal rebatedFee; + private BigDecimal rebatedFee; @JsonProperty("rebated_fee_currency") @JsonDeserialize(converter = StringToCurrencyConverter.class) - Currency rebatedFeeCurrency; + private Currency rebatedFeeCurrency; @JsonProperty("stp_id") - Integer stpId; + private Integer stpId; @JsonProperty("stp_act") - String stpAction; + private String stpAction; @JsonProperty("finish_as") - String finishAs; + private String finishAs; + } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioTimeInForce.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioTimeInForce.java new file mode 100644 index 00000000000..3238082763c --- /dev/null +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/dto/trade/GateioTimeInForce.java @@ -0,0 +1,8 @@ +package org.knowm.xchange.gateio.dto.trade; + +public enum GateioTimeInForce { + GTC, // GoodTillCancelled + IOC, //ImmediateOrCancelled, taker only + POC, //PendingOrCancelled, makes a post-only order that always enjoys a maker fee + FOK; // FillOrKill, fill either completely or none +} diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountService.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountService.java index 612167dba06..dc4812f93bd 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountService.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountService.java @@ -1,30 +1,31 @@ package org.knowm.xchange.gateio.service; -import java.io.IOException; -import java.util.EnumSet; -import java.util.List; -import java.util.stream.Collectors; import org.apache.commons.lang3.Validate; -import org.knowm.xchange.dto.account.AccountInfo; -import org.knowm.xchange.dto.account.Balance; -import org.knowm.xchange.dto.account.FundingRecord; -import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.client.ResilienceRegistries; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.account.*; import org.knowm.xchange.gateio.GateioAdapters; import org.knowm.xchange.gateio.GateioErrorAdapter; import org.knowm.xchange.gateio.GateioExchange; import org.knowm.xchange.gateio.dto.GateioException; -import org.knowm.xchange.gateio.dto.account.GateioCurrencyBalance; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRecord; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRequest; +import org.knowm.xchange.gateio.dto.account.*; import org.knowm.xchange.gateio.service.params.GateioWithdrawFundsParams; +import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.account.AccountService; import org.knowm.xchange.service.trade.params.TradeHistoryParams; import org.knowm.xchange.service.trade.params.WithdrawFundsParams; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public class GateioAccountService extends GateioAccountServiceRaw implements AccountService { - public GateioAccountService(GateioExchange exchange) { - super(exchange); + public GateioAccountService(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } @Override @@ -82,4 +83,49 @@ public List getFundingHistory(TradeHistoryParams params) throws I throw GateioErrorAdapter.adapt(e); } } + + /** + * set leverage for futures contract + * Isolated margin only + * + * @param instrument symbol to change leverage + * @param leverage leverage + * @return is successful + * @throws IOException + */ + @Override + public boolean setLeverage(Instrument instrument, int leverage) throws IOException { + if (instrument instanceof FuturesContract) { + String settle = instrument.getCounter().getCurrencyCode().toLowerCase(); + String contract = GateioAdapters.toGateioInstrument(instrument); + setLeverage(settle, contract, String.valueOf(leverage)); + return true; + } else throw new UnsupportedOperationException("Leverage is not supported for spot instruments"); + } + + @Override + public Map getDynamicTradingFeesByInstrument(String... category) + throws IOException { + try { + Map fees = new HashMap<>(); + if (exchange.isFuturesEnabled()) { + Map futuresFees = getFuturesFee("usdt", null); + futuresFees.forEach((contract, fee) -> { + fees.put( + GateioAdapters.fromGateioInstrument(contract, true), + new Fee(fee.getMakerFee(), fee.getTakerFee())); + }); + } else { + GateioSpotFee spotFee = getSpotFee(null); + exchange.getExchangeMetaData().getInstruments().keySet().forEach(instrument -> { + fees.put(instrument, + new Fee(spotFee.getMakerFee(), spotFee.getTakerFee())); + }); + } + return fees; + } catch (GateioException e) { + throw GateioErrorAdapter.adapt(e); + } + } + } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountServiceRaw.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountServiceRaw.java index 87ccacb827a..35974e216c3 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountServiceRaw.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioAccountServiceRaw.java @@ -1,21 +1,11 @@ package org.knowm.xchange.gateio.service; -import java.io.IOException; -import java.util.List; -import java.util.Objects; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.gateio.GateioErrorAdapter; import org.knowm.xchange.gateio.GateioExchange; import org.knowm.xchange.gateio.dto.GateioException; -import org.knowm.xchange.gateio.dto.account.GateioAccountBookRecord; -import org.knowm.xchange.gateio.dto.account.GateioAddressRecord; -import org.knowm.xchange.gateio.dto.account.GateioCurrencyBalance; -import org.knowm.xchange.gateio.dto.account.GateioDepositAddress; -import org.knowm.xchange.gateio.dto.account.GateioDepositRecord; -import org.knowm.xchange.gateio.dto.account.GateioSubAccountTransfer; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawStatus; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRecord; -import org.knowm.xchange.gateio.dto.account.GateioWithdrawalRequest; +import org.knowm.xchange.gateio.dto.account.*; import org.knowm.xchange.gateio.dto.account.params.GateioSubAccountTransfersParams; import org.knowm.xchange.gateio.service.params.GateioDepositsParams; import org.knowm.xchange.gateio.service.params.GateioFundingHistoryParams; @@ -24,10 +14,18 @@ import org.knowm.xchange.service.trade.params.TradeHistoryParams; import org.knowm.xchange.service.trade.params.TradeHistoryParamsTimeSpan; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.knowm.xchange.gateio.GateioResilience.DYNAMIC_TRADING_FEE_RATE_LIMITER; +import static org.knowm.xchange.gateio.GateioResilience.LEVERAGE_RATE_LIMITER; + public class GateioAccountServiceRaw extends GateioBaseService { - public GateioAccountServiceRaw(GateioExchange exchange) { - super(exchange); + public GateioAccountServiceRaw(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } public GateioDepositAddress getDepositAddress(Currency currency) throws IOException { @@ -58,6 +56,24 @@ public List getSpotBalances(Currency currency) throws IOE apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, currencyCode); } + public GateioSpotFee getSpotFee(String currencyPair) throws IOException { + return decorateApiCall( + () -> + gateioV4Authenticated.getSpotFee( + apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, currencyPair)) + .withRateLimiter(rateLimiter(DYNAMIC_TRADING_FEE_RATE_LIMITER)) + .call(); + } + + public Map getFuturesFee(String settle, String contract) throws IOException { + return decorateApiCall( + () -> + gateioV4Authenticated.getFuturesFee( + apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, settle, contract)) + .withRateLimiter(rateLimiter(DYNAMIC_TRADING_FEE_RATE_LIMITER)) + .call(); + } + public List getWithdrawals(GateioWithdrawalsParams params) throws IOException { String currency = params.getCurrency() != null ? params.getCurrency().toString() : null; @@ -160,4 +176,25 @@ public List getSubAccountTransfers( params.getPageLength(), params.getZeroBasedPageNumber()); } + + public void setLeverage(String settle, String contract, String leverage) throws IOException { + try { + decorateApiCall( + () -> { + GateioPositionLeverageUpdate positionLeverageUpdate = + gateioV4Authenticated.updatePositionLeverage( + apiKey, + exchange.getNonceFactory(), + gateioV4ParamsDigest, + settle, + contract, + leverage); + Objects.requireNonNull(positionLeverageUpdate); + return null; + }).withRateLimiter(rateLimiter(LEVERAGE_RATE_LIMITER)) + .call(); + } catch (GateioException e) { + throw GateioErrorAdapter.adapt(e); + } + } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioBaseService.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioBaseService.java index 03f530d391f..a9bb52a002b 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioBaseService.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioBaseService.java @@ -2,16 +2,17 @@ import lombok.SneakyThrows; import org.knowm.xchange.client.ExchangeRestProxyBuilder; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.gateio.Gateio; import org.knowm.xchange.gateio.GateioExchange; import org.knowm.xchange.gateio.GateioV4Authenticated; import org.knowm.xchange.gateio.config.Config; import org.knowm.xchange.gateio.config.GateioJacksonObjectMapperFactory; -import org.knowm.xchange.service.BaseExchangeService; +import org.knowm.xchange.service.BaseResilientExchangeService; import org.knowm.xchange.service.BaseService; import si.mazi.rescu.ParamsDigest; -public class GateioBaseService extends BaseExchangeService implements BaseService { +public class GateioBaseService extends BaseResilientExchangeService implements BaseService { protected final String apiKey; protected final Gateio gateio; @@ -19,8 +20,8 @@ public class GateioBaseService extends BaseExchangeService imple protected final ParamsDigest gateioV4ParamsDigest; @SneakyThrows - public GateioBaseService(GateioExchange exchange) { - super(exchange); + public GateioBaseService(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); gateio = ExchangeRestProxyBuilder.forInterface(Gateio.class, exchange.getExchangeSpecification()) diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataService.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataService.java index c3bb6447b7d..2ee496ebae2 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataService.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataService.java @@ -1,15 +1,11 @@ package org.knowm.xchange.gateio.service; -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; import org.apache.commons.lang3.Validate; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.marketdata.CandleStickData; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.meta.ExchangeHealth; @@ -19,19 +15,27 @@ import org.knowm.xchange.gateio.GateioExchange; import org.knowm.xchange.gateio.config.Config; import org.knowm.xchange.gateio.dto.GateioException; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyInfo; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyPairDetails; -import org.knowm.xchange.gateio.dto.marketdata.GateioOrderBook; -import org.knowm.xchange.gateio.dto.marketdata.GateioTicker; +import org.knowm.xchange.gateio.dto.marketdata.*; import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.marketdata.MarketDataService; import org.knowm.xchange.service.marketdata.params.Params; +import org.knowm.xchange.service.trade.params.CandleStickDataParams; +import org.knowm.xchange.service.trade.params.DefaultCandleStickParam; +import org.knowm.xchange.service.trade.params.DefaultCandleStickParamWithLimit; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; public class GateioMarketDataService extends GateioMarketDataServiceRaw implements MarketDataService { - public GateioMarketDataService(GateioExchange exchange) { - super(exchange); + public GateioMarketDataService(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } @Override @@ -60,10 +64,16 @@ public Ticker getTicker(CurrencyPair currencyPair, Object... args) throws IOExce public Ticker getTicker(Instrument instrument, Object... args) throws IOException { Objects.requireNonNull(instrument); try { - List tickers = getGateioTickers(instrument); - Validate.validState(tickers.size() == 1); - - return GateioAdapters.toTicker(tickers.get(0)); + if (exchange.isFuturesEnabled()) { + List tickers = getGateioFuturesTickers(instrument); + Validate.validState(tickers.size() == 1); + return GateioAdapters.toTickerFutures( + tickers.get(0), exchange.getExchangeMetaData().getInstruments().get(instrument).getContractValue()); + } else { + List tickers = getGateioTickers(instrument); + Validate.validState(tickers.size() == 1); + return GateioAdapters.toTickerSpot(tickers.get(0)); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } @@ -72,9 +82,24 @@ public Ticker getTicker(Instrument instrument, Object... args) throws IOExceptio @Override public List getTickers(Params params) throws IOException { try { - List tickers = getGateioTickers(null); - - return tickers.stream().map(GateioAdapters::toTicker).collect(Collectors.toList()); + if (exchange.isFuturesEnabled()) { + List tickers = getGateioFuturesTickers(null); + return tickers.stream() + .map( + d -> + GateioAdapters.toTickerFutures( + d, + exchange.getExchangeMetaData().getInstruments() + .get(GateioAdapters.fromGateioInstrument(d.getContract(), true)) + .getContractValue())) + .collect(Collectors.toList()); + } else { + List tickers = getGateioTickers(null); + return tickers.stream() + .map( + GateioAdapters::toTickerSpot) + .collect(Collectors.toList()); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } @@ -122,18 +147,105 @@ public List getCurrencyPairs() throws IOException { public Map getMetaDataByInstrument() throws IOException { try { - List metadata = getCurrencyPairDetails(); + if (exchange.isFuturesEnabled()) { + List metadata = getInstrumentDetails(); + return metadata.stream().filter(f -> f.getType().equals("direct") && + f.getStatus().equals("trading")) + .collect( + Collectors.toMap( + gateioInstrumentDetails -> + new FuturesContract( + new CurrencyPair(gateioInstrumentDetails.getName().replace("_", "/")), + "PERP"), + GateioAdapters::instrumentToInstrumentMetaData)); + } else { + List metadata = getCurrencyPairDetails(); + + return metadata.stream() + .collect( + Collectors.toMap( + gateioCurrencyPairDetails -> + new CurrencyPair( + gateioCurrencyPairDetails.getAsset(), + gateioCurrencyPairDetails.getQuote()), + GateioAdapters::currencyPairToInstrumentMetaData)); + } + } catch (GateioException e) { + throw GateioErrorAdapter.adapt(e); + } + } - return metadata.stream() - .collect( - Collectors.toMap( - gateioCurrencyPairDetails -> - new CurrencyPair( - gateioCurrencyPairDetails.getAsset(), - gateioCurrencyPairDetails.getQuote()), - GateioAdapters::toInstrumentMetaData)); + @Override + public CandleStickData getCandleStickData(CurrencyPair currencyPair, CandleStickDataParams params) + throws IOException { + return getCandleStickData((Instrument) currencyPair, params); + } + + /** + * K-line chart data returns a maximum of 1000 points per request. When specifying from, to, and interval, ensure the number of points is not excessive + * + * @param instrument instrument. + * @param params Params for query, including start(e.g. march 2022.) and end date, period etc., + * @return + * @throws IOException + */ + @Override + public CandleStickData getCandleStickData(Instrument instrument, CandleStickDataParams params) + throws IOException { + Long from = null; + Long to = null; + Integer limit = null; + String interval = "1h"; // default + if (params instanceof DefaultCandleStickParamWithLimit) { + DefaultCandleStickParamWithLimit p = + (DefaultCandleStickParamWithLimit) params; + limit = p.getLimit(); + if (p.getPeriodInSecs() > 0) { + interval = adaptInterval(p.getPeriodInSecs()); + } + } + // limit OR (from, to) + else if (params instanceof DefaultCandleStickParam) { + DefaultCandleStickParam p = + (DefaultCandleStickParam) params; + if (p.getStartDate() != null) { + from = p.getStartDate().getTime() / 1000; + } + if (p.getEndDate() != null) { + to = p.getEndDate().getTime() / 1000; + } + if (p.getPeriodInSecs() > 0) { + interval = adaptInterval(p.getPeriodInSecs()); + } + } + try { + if (instrument instanceof FuturesContract) { + List gateiFuturesCandlesticks = getGateioFuturesCandlesticks(instrument, limit, from, to, interval); + return GateioAdapters.toCandleStickDataFutures(gateiFuturesCandlesticks, instrument, exchange.getExchangeMetaData().getInstruments().get(instrument).getContractValue()); + } else { + List gateioSpotCandlesticks = getGateioSpotCandlesticks(instrument, limit, from, to, interval); + return GateioAdapters.toCandleStickDataSpot(gateioSpotCandlesticks, instrument); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } } + + public List getFundingRateHistory(Instrument instrument, Long startTime, Long endTime, Integer limit) throws IOException { + return getGateioFundingRateHistory(instrument, startTime, endTime, limit); + } + private String adaptInterval(long periodInSecs) { + if (periodInSecs == 10) return "10s"; + if (periodInSecs == 60) return "1m"; + if (periodInSecs == 300) return "5m"; + if (periodInSecs == 900) return "15m"; + if (periodInSecs == 1800) return "30m"; + if (periodInSecs == 3600) return "1h"; + if (periodInSecs == 14400) return "4h"; + if (periodInSecs == 28800) return "8h"; + if (periodInSecs == 86400) return "1d"; + if (periodInSecs == 604800) return "7d"; + if (periodInSecs == 2592000) return "30d"; + return "1h"; + } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataServiceRaw.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataServiceRaw.java index 3affd6ba5c0..4573ecd0cf8 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataServiceRaw.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioMarketDataServiceRaw.java @@ -1,22 +1,21 @@ package org.knowm.xchange.gateio.service; -import java.io.IOException; -import java.util.List; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.gateio.GateioAdapters; import org.knowm.xchange.gateio.GateioExchange; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyChain; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyInfo; -import org.knowm.xchange.gateio.dto.marketdata.GateioCurrencyPairDetails; -import org.knowm.xchange.gateio.dto.marketdata.GateioOrderBook; -import org.knowm.xchange.gateio.dto.marketdata.GateioServerTime; -import org.knowm.xchange.gateio.dto.marketdata.GateioTicker; +import org.knowm.xchange.gateio.dto.marketdata.*; import org.knowm.xchange.instrument.Instrument; +import java.io.IOException; +import java.util.List; + +import static org.knowm.xchange.gateio.GateioResilience.*; + public class GateioMarketDataServiceRaw extends GateioBaseService { - public GateioMarketDataServiceRaw(GateioExchange exchange) { - super(exchange); + public GateioMarketDataServiceRaw(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } public GateioServerTime getGateioServerTime() throws IOException { @@ -24,7 +23,19 @@ public GateioServerTime getGateioServerTime() throws IOException { } public List getGateioTickers(Instrument instrument) throws IOException { - return gateio.getTickers(GateioAdapters.toString(instrument)); + return decorateApiCall( + () -> gateio.getTickers(GateioAdapters.toGateioInstrument(instrument))) + .withRateLimiter(rateLimiter(TICKERS_RATE_LIMITER)) + .call(); + } + + public List getGateioFuturesTickers(Instrument instrument) + throws IOException { + String settle = "usdt"; + return decorateApiCall( + () -> gateio.getFuturesTickers(settle, GateioAdapters.toGateioInstrument(instrument))) + .withRateLimiter(rateLimiter(TICKERS_RATE_LIMITER)) + .call(); } public List getGateioCurrencyInfos() throws IOException { @@ -32,7 +43,7 @@ public List getGateioCurrencyInfos() throws IOException { } public GateioOrderBook getGateioOrderBook(Instrument instrument) throws IOException { - return gateio.getOrderBook(GateioAdapters.toString(instrument), false); + return gateio.getOrderBook(GateioAdapters.toGateioInstrument(instrument), false); } public List getCurrencyChains(Currency currency) throws IOException { @@ -43,8 +54,48 @@ public List getCurrencyPairDetails() throws IOExcepti return gateio.getCurrencyPairDetails(); } + public List getInstrumentDetails() throws IOException { + return gateio.getInstrumentDetails(); + } + public GateioCurrencyPairDetails getCurrencyPairDetails(Instrument instrument) throws IOException { - return gateio.getCurrencyPairDetails(GateioAdapters.toString(instrument)); + return gateio.getCurrencyPairDetails(GateioAdapters.toGateioInstrument(instrument)); + } + + public List getGateioSpotCandlesticks( + Instrument instrument, Integer limit, Long from, Long to, String interval) + throws IOException { + return decorateApiCall( + () -> + gateio.getSpotCandlesticks( + GateioAdapters.toGateioInstrument(instrument), limit, from, to, interval)) + .withRateLimiter(rateLimiter(CANDLESTICK_DATA_RATE_LIMITER)) + .call(); + } + + public List getGateioFuturesCandlesticks( + Instrument instrument, Integer limit, Long from, Long to, String interval) + throws IOException { + return decorateApiCall( + () -> + gateio.getFuturesCandlesticks(instrument.getCounter().toString().toLowerCase(), + GateioAdapters.toGateioInstrument(instrument), limit, from, to, interval)) + .withRateLimiter(rateLimiter(CANDLESTICK_DATA_RATE_LIMITER)) + .call(); + } + + public List getGateioFundingRateHistory( + Instrument instrument, Long startTime, Long endTime, Integer limit) + throws IOException { + return decorateApiCall( + () -> gateio.getFundingRateHistory( + instrument.getCounter().toString().toLowerCase(), + GateioAdapters.toGateioInstrument(instrument), + limit, + startTime, + endTime)) + .withRateLimiter(rateLimiter(FUNDING_HISTORY_RATE_LIMITER)) + .call(); } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeService.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeService.java index c5983b1fc9c..eb91879a109 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeService.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeService.java @@ -1,24 +1,20 @@ package org.knowm.xchange.gateio.service; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; +import jakarta.ws.rs.NotSupportedException; import org.apache.commons.lang3.Validate; +import org.knowm.xchange.client.ResilienceRegistries; +import org.knowm.xchange.derivative.FuturesContract; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderStatus; import org.knowm.xchange.dto.marketdata.Trades.TradeSortType; -import org.knowm.xchange.dto.trade.LimitOrder; -import org.knowm.xchange.dto.trade.MarketOrder; -import org.knowm.xchange.dto.trade.OpenOrders; -import org.knowm.xchange.dto.trade.UserTrade; -import org.knowm.xchange.dto.trade.UserTrades; +import org.knowm.xchange.dto.trade.*; import org.knowm.xchange.gateio.GateioAdapters; import org.knowm.xchange.gateio.GateioErrorAdapter; import org.knowm.xchange.gateio.GateioExchange; import org.knowm.xchange.gateio.dto.GateioException; -import org.knowm.xchange.gateio.dto.account.GateioOrder; +import org.knowm.xchange.gateio.dto.trade.GateioCancelOrderParams; +import org.knowm.xchange.gateio.dto.trade.GateioFuturesOrderResponse; +import org.knowm.xchange.gateio.dto.trade.GateioSpotOrderResponse; import org.knowm.xchange.gateio.service.params.GateioTradeHistoryParams; import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.trade.TradeService; @@ -30,10 +26,14 @@ import org.knowm.xchange.service.trade.params.orders.OrderQueryParamInstrument; import org.knowm.xchange.service.trade.params.orders.OrderQueryParams; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + public class GateioTradeService extends GateioTradeServiceRaw implements TradeService { - public GateioTradeService(GateioExchange exchange) { - super(exchange); + public GateioTradeService(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } @Override @@ -51,8 +51,13 @@ public OpenOrders getOpenOrders(OpenOrdersParams params) throws IOException { @Override public String placeMarketOrder(MarketOrder marketOrder) throws IOException { try { - GateioOrder order = createOrder(GateioAdapters.toGateioOrder(marketOrder)); - return order.getId(); + if (marketOrder.getInstrument() instanceof FuturesContract) { + GateioFuturesOrderResponse order = createFuturesOrder(GateioAdapters.toGateioFuturesOrder(marketOrder, exchange.getExchangeMetaData().getInstruments().get(marketOrder.getInstrument()).getContractValue())); + return String.valueOf(order.getId()); + } else { + GateioSpotOrderResponse order = createOrder(GateioAdapters.toGateioSpotOrderRequest(marketOrder)); + return order.getId(); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } @@ -61,8 +66,14 @@ public String placeMarketOrder(MarketOrder marketOrder) throws IOException { @Override public String placeLimitOrder(LimitOrder limitOrder) throws IOException { try { - GateioOrder order = createOrder(GateioAdapters.toGateioOrder(limitOrder)); - return order.getId(); + if (limitOrder.getInstrument() instanceof FuturesContract) { + GateioFuturesOrderResponse order = createFuturesOrder(GateioAdapters.toGateioFuturesOrder(limitOrder + , exchange.getExchangeMetaData().getInstruments().get(limitOrder.getInstrument()).getContractValue())); + return String.valueOf(order.getId()); + } else { + GateioSpotOrderResponse order = createOrder(GateioAdapters.toGateioSpotOrderRequest(limitOrder)); + return order.getId(); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } @@ -77,17 +88,30 @@ public Collection getOrder(OrderQueryParams... orderQueryParams) throws I OrderQueryParamInstrument params = (OrderQueryParamInstrument) orderQueryParams[0]; try { - GateioOrder gateioOrder = getOrder(params.getOrderId(), params.getInstrument()); - return Collections.singletonList(GateioAdapters.toOrder(gateioOrder)); + if (params.getInstrument() instanceof FuturesContract) { + GateioFuturesOrderResponse gateioOrder = getFuturesOrder(params.getOrderId(), params.getInstrument()); + return Collections.singletonList(GateioAdapters.toOrder(gateioOrder)); + } else { + GateioSpotOrderResponse gateioOrder = getOrder(params.getOrderId(), params.getInstrument()); + return Collections.singletonList(GateioAdapters.toOrder(gateioOrder)); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } } + /** + * it's possbile to use UserReferenceId(text in Gateio) as orderId + */ public Order cancelOrder(String orderId, Instrument instrument) throws IOException { try { - GateioOrder gateioOrder = cancelOrderRaw(orderId, instrument); - return GateioAdapters.toOrder(gateioOrder); + if (instrument instanceof FuturesContract) { + GateioFuturesOrderResponse gateioOrder = cancelFuturesOrderRaw(orderId, instrument); + return GateioAdapters.toOrder(gateioOrder); + } else { + GateioSpotOrderResponse gateioOrder = cancelOrderRaw(orderId, instrument); + return GateioAdapters.toOrder(gateioOrder); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } @@ -95,13 +119,51 @@ public Order cancelOrder(String orderId, Instrument instrument) throws IOExcepti @Override public boolean cancelOrder(CancelOrderParams orderParams) throws IOException { - Validate.isInstanceOf(DefaultCancelOrderByInstrumentAndIdParams.class, orderParams); - DefaultCancelOrderByInstrumentAndIdParams params = - (DefaultCancelOrderByInstrumentAndIdParams) orderParams; + try { + String id = ""; + Instrument instrument = null; + if (orderParams instanceof DefaultCancelOrderByInstrumentAndIdParams) { + DefaultCancelOrderByInstrumentAndIdParams params = + (DefaultCancelOrderByInstrumentAndIdParams) orderParams; + id = params.getOrderId(); + instrument = params.getInstrument(); + } else { + if (orderParams instanceof GateioCancelOrderParams) { + GateioCancelOrderParams params = (GateioCancelOrderParams) orderParams; + instrument = params.getInstrument(); + if (params.getUserReference() != null) { + id = params.getUserReference(); + } else if (params.getOrderId() != null) { + id = params.getOrderId(); + } + } + } + if (!id.isEmpty() && instrument != null) { + Order order = cancelOrder(id, instrument); + return order.getStatus() == OrderStatus.CANCELED; + } else throw new NotSupportedException("id or instrument is empty"); + } catch (GateioException e) { + throw GateioErrorAdapter.adapt(e); + } + } + @Override + public String changeOrder(LimitOrder limitOrder) throws IOException { try { - Order order = cancelOrder(params.getOrderId(), params.getInstrument()); - return order.getStatus() == OrderStatus.CANCELED; + Map request = new HashMap<>(); + request.put("amount", limitOrder.getOriginalAmount()); + request.put("price", limitOrder.getLimitPrice()); + String id; + if (limitOrder.getUserReference() != null) { + id = limitOrder.getUserReference(); + } else id = limitOrder.getId(); + if (limitOrder.getInstrument() instanceof FuturesContract) { + GateioFuturesOrderResponse response = amendFuturesOrder(id, limitOrder.getInstrument(), request); + return String.valueOf(response.getId()); + } else { + GateioSpotOrderResponse response = amendSpotOrder(id, limitOrder.getInstrument(), request); + return response.getId(); + } } catch (GateioException e) { throw GateioErrorAdapter.adapt(e); } @@ -122,7 +184,7 @@ public UserTrades getTradeHistory(TradeHistoryParams params) throws IOException @Override public Class[] getRequiredCancelOrderParamClasses() { - return new Class[] {DefaultCancelOrderByInstrumentAndIdParams.class}; + return new Class[]{DefaultCancelOrderByInstrumentAndIdParams.class}; } @Override diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeServiceRaw.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeServiceRaw.java index 10776e8a3aa..dbdbb187266 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeServiceRaw.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioTradeServiceRaw.java @@ -1,34 +1,29 @@ package org.knowm.xchange.gateio.service; -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; import org.knowm.xchange.dto.Order.OrderStatus; import org.knowm.xchange.gateio.GateioAdapters; import org.knowm.xchange.gateio.GateioExchange; -import org.knowm.xchange.gateio.dto.account.GateioOrder; -import org.knowm.xchange.gateio.dto.trade.GateioUserTradeRaw; +import org.knowm.xchange.gateio.dto.trade.*; import org.knowm.xchange.instrument.Instrument; -import org.knowm.xchange.service.trade.params.CurrencyPairParam; -import org.knowm.xchange.service.trade.params.TradeHistoryParamCurrencyPair; -import org.knowm.xchange.service.trade.params.TradeHistoryParamPaging; -import org.knowm.xchange.service.trade.params.TradeHistoryParamTransactionId; -import org.knowm.xchange.service.trade.params.TradeHistoryParams; -import org.knowm.xchange.service.trade.params.TradeHistoryParamsTimeSpan; +import org.knowm.xchange.service.trade.params.*; + +import java.io.IOException; +import java.util.*; + +import static org.knowm.xchange.gateio.GateioResilience.ORDERS_RATE_LIMITER; public class GateioTradeServiceRaw extends GateioBaseService { - public GateioTradeServiceRaw(GateioExchange exchange) { - super(exchange); + public GateioTradeServiceRaw(GateioExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } - public List listOrders(Instrument instrument, OrderStatus orderStatus) + public List listOrders(Instrument instrument, OrderStatus orderStatus) throws IOException { // validate arguments Objects.requireNonNull(orderStatus); @@ -43,8 +38,8 @@ public List listOrders(Instrument instrument, OrderStatus orderStat apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, - GateioAdapters.toString(instrument), - GateioAdapters.toString(orderStatus)); + GateioAdapters.toGateioInstrument(instrument), + GateioAdapters.toGateioInstrument(orderStatus)); } public List getGateioUserTrades(TradeHistoryParams params) @@ -90,7 +85,7 @@ public List getGateioUserTrades(TradeHistoryParams params) apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, - GateioAdapters.toString(currencyPair), + GateioAdapters.toGateioInstrument(currencyPair), 1000, currentPageNumber, orderId, @@ -108,7 +103,7 @@ public List getGateioUserTrades(TradeHistoryParams params) apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, - GateioAdapters.toString(currencyPair), + GateioAdapters.toGateioInstrument(currencyPair), pageLength, pageNumber, orderId, @@ -117,26 +112,92 @@ public List getGateioUserTrades(TradeHistoryParams params) to); } - public GateioOrder createOrder(GateioOrder gateioOrder) throws IOException { - return gateioV4Authenticated.createOrder( - apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, gateioOrder); + public GateioSpotOrderResponse createOrder(GateioSpotOrderRequest gateioOrder) throws IOException { + return decorateApiCall( + () -> + gateioV4Authenticated.createOrder( + apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, gateioOrder)) + .withRateLimiter(rateLimiter(ORDERS_RATE_LIMITER)) + .call(); } - public GateioOrder getOrder(String orderId, Instrument instrument) throws IOException { + public GateioFuturesOrderResponse createFuturesOrder(GateioFuturesOrderRequest gateioFuturesOrder) throws IOException { + Instrument instrument = GateioAdapters.fromGateioInstrument(gateioFuturesOrder.getContract(), true); + String settle = (instrument instanceof FuturesContract) ? instrument.getCounter().getCurrencyCode().toLowerCase() : "usdt"; + return decorateApiCall( + () -> + gateioV4Authenticated.createFuturesOrder( + apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, null, settle, gateioFuturesOrder)) + .withRateLimiter(rateLimiter(ORDERS_RATE_LIMITER)) + .call(); + } + + public GateioSpotOrderResponse getOrder(String orderId, Instrument instrument) throws IOException { return gateioV4Authenticated.getOrder( apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, orderId, - GateioAdapters.toString(instrument)); + GateioAdapters.toGateioInstrument(instrument)); + } + + public GateioFuturesOrderResponse getFuturesOrder(String orderId, Instrument instrument) throws IOException { + String settle = (instrument instanceof FuturesContract) ? instrument.getCounter().getCurrencyCode().toLowerCase() : "usdt"; + return gateioV4Authenticated.getFuturesOrder( + apiKey, + exchange.getNonceFactory(), + gateioV4ParamsDigest, + null, + settle, + orderId); } - public GateioOrder cancelOrderRaw(String orderId, Instrument instrument) throws IOException { + public GateioSpotOrderResponse cancelOrderRaw(String orderId, Instrument instrument) throws IOException { return gateioV4Authenticated.cancelOrder( apiKey, exchange.getNonceFactory(), gateioV4ParamsDigest, orderId, - GateioAdapters.toString(instrument)); + GateioAdapters.toGateioInstrument(instrument)); + } + + public GateioFuturesOrderResponse cancelFuturesOrderRaw(String orderId, Instrument instrument) throws IOException { + String settle = (instrument instanceof FuturesContract) ? instrument.getCounter().getCurrencyCode().toLowerCase() : "usdt"; + return gateioV4Authenticated.cancelFuturesOrder( + apiKey, + exchange.getNonceFactory(), + gateioV4ParamsDigest, + null, + settle, + orderId); + } + + public GateioSpotOrderResponse amendSpotOrder(String orderId, Instrument instrument, Map request) throws IOException { + return decorateApiCall( + () -> + gateioV4Authenticated.amendOrder( + apiKey, + exchange.getNonceFactory(), + gateioV4ParamsDigest, + orderId, + GateioAdapters.toGateioInstrument(instrument), + request)) + .withRateLimiter(rateLimiter(ORDERS_RATE_LIMITER)) + .call(); + } + + public GateioFuturesOrderResponse amendFuturesOrder(String orderId, Instrument instrument, Map request) throws IOException { + String settle = instrument.getCounter().getCurrencyCode().toLowerCase(); + return decorateApiCall( + () -> + gateioV4Authenticated.amendFuturesOrder( + apiKey, + exchange.getNonceFactory(), + gateioV4ParamsDigest, + null, + settle, + orderId, + request)).withRateLimiter(rateLimiter(ORDERS_RATE_LIMITER)) + .call(); } } diff --git a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioV4Digest.java b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioV4Digest.java index f7b439ac247..8cba163a706 100644 --- a/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioV4Digest.java +++ b/xchange-gateio-v4/src/main/java/org/knowm/xchange/gateio/service/GateioV4Digest.java @@ -1,13 +1,15 @@ package org.knowm.xchange.gateio.service; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import javax.crypto.Mac; import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; import org.knowm.xchange.service.BaseParamsDigest; import org.knowm.xchange.utils.DigestUtils; import si.mazi.rescu.RestInvocation; +import javax.crypto.Mac; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + public final class GateioV4Digest extends BaseParamsDigest { private GateioV4Digest(String secretKeyBase64) { @@ -25,8 +27,8 @@ public String digestParams(RestInvocation restInvocation) { String method = restInvocation.getHttpMethod(); String path = restInvocation.getPath(); - String query = restInvocation.getQueryString(); - String body = restInvocation.getRequestBody(); + String query = StringUtils.defaultString(restInvocation.getQueryString()); + String body = StringUtils.defaultString(restInvocation.getRequestBody()); MessageDigest md = MessageDigest.getInstance("SHA-512"); String hexedHashedBody = DigestUtils.bytesToHex(md.digest(body.getBytes(StandardCharsets.UTF_8))); diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/GateioAdaptersTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/GateioAdaptersTest.java new file mode 100644 index 00000000000..223d2cac1eb --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/GateioAdaptersTest.java @@ -0,0 +1,84 @@ +package org.knowm.xchange.gateio; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.marketdata.CandleStickData; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.gateio.dto.marketdata.GateioFuturesCandlestick; +import org.knowm.xchange.gateio.dto.marketdata.GateioInstrumentDetails; +import org.knowm.xchange.gateio.dto.marketdata.GateioSpotCandlestick; +import org.knowm.xchange.utils.ObjectMapperHelper; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +class GateioAdaptersTest { + + @Test + void testToCandleStickDataSpotFutures() throws IOException { + try (InputStream is = getClass().getResourceAsStream("/__files/api_v4_futures_candlesticks.json")) { + Assertions.assertNotNull(is); + GateioFuturesCandlestick[] candlesticks = ObjectMapperHelper.readValue(new String(is.readAllBytes()), GateioFuturesCandlestick[].class); + + FuturesContract instrument = new FuturesContract("BTC/USDT/PERP"); + CandleStickData candleStickData = GateioAdapters.toCandleStickDataFutures( + Arrays.asList(candlesticks), instrument, new BigDecimal("0.0001")); + + assertThat(candleStickData.getInstrument()).isEqualTo(instrument); + assertThat(candleStickData.getCandleSticks()).hasSize(1); + assertThat(candleStickData.getCandleSticks().get(0).getOpen()).isEqualTo(new BigDecimal("100")); + assertThat(candleStickData.getCandleSticks().get(0).getHigh()).isEqualTo(new BigDecimal("110")); + assertThat(candleStickData.getCandleSticks().get(0).getLow()).isEqualTo(new BigDecimal("90")); + assertThat(candleStickData.getCandleSticks().get(0).getClose()).isEqualTo(new BigDecimal("105")); + assertThat(candleStickData.getCandleSticks().get(0).getVolume()).isEqualTo(new BigDecimal("0.001")); + assertThat(candleStickData.getCandleSticks().get(0).getQuotaVolume()).isEqualTo(new BigDecimal("1000")); + } + } + + @Test + void testToCandleStickDataSpotSpot() throws IOException { + try (InputStream is = getClass().getResourceAsStream("/__files/api_v4_spot_candlesticks.json")) { + Assertions.assertNotNull(is); + GateioSpotCandlestick[] candlesticks = ObjectMapperHelper.readValue(new String(is.readAllBytes()), GateioSpotCandlestick[].class); + + CurrencyPair instrument = CurrencyPair.BTC_USDT; + CandleStickData candleStickData = GateioAdapters.toCandleStickDataSpot( + Arrays.asList(candlesticks), instrument); + + assertThat(candleStickData.getInstrument()).isEqualTo(instrument); + assertThat(candleStickData.getCandleSticks()).hasSize(1); + assertThat(candleStickData.getCandleSticks().get(0).getOpen()).isEqualTo(new BigDecimal("100")); + assertThat(candleStickData.getCandleSticks().get(0).getHigh()).isEqualTo(new BigDecimal("110")); + assertThat(candleStickData.getCandleSticks().get(0).getLow()).isEqualTo(new BigDecimal("90")); + assertThat(candleStickData.getCandleSticks().get(0).getClose()).isEqualTo(new BigDecimal("105")); + assertThat(candleStickData.getCandleSticks().get(0).getVolume()).isEqualTo(new BigDecimal("1000")); + assertThat(candleStickData.getCandleSticks().get(0).getQuotaVolume()).isEqualTo(new BigDecimal("10")); + } + } + + @Test + void testInstrumentToInstrumentMetaData() throws IOException { + try (InputStream is = getClass().getResourceAsStream("/__files/api_v4_instrument_details.json")) { + Assertions.assertNotNull(is); + GateioInstrumentDetails[] detailsArray = ObjectMapperHelper.readValue(new String(is.readAllBytes()), GateioInstrumentDetails[].class); + GateioInstrumentDetails details = detailsArray[0]; + + InstrumentMetaData metaData = GateioAdapters.instrumentToInstrumentMetaData(details); + + assertThat(metaData.getTradingFee()).isEqualByComparingTo(new BigDecimal("0.00075")); + assertThat(metaData.getMinimumAmount()).isEqualByComparingTo(new BigDecimal("0.0001")); + assertThat(metaData.getMaximumAmount()).isEqualByComparingTo(new BigDecimal("100")); + assertThat(metaData.getPriceStepSize()).isEqualByComparingTo(new BigDecimal("0.1")); + assertThat(metaData.getAmountStepSize()).isEqualByComparingTo(new BigDecimal("0.0001")); + assertThat(metaData.getVolumeScale()).isEqualTo(4); + assertThat(metaData.getPriceScale()).isEqualTo(1); + assertThat(metaData.getContractValue()).isEqualByComparingTo(new BigDecimal("0.0001")); + } + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/account/GateioFuturesFeeTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/account/GateioFuturesFeeTest.java new file mode 100644 index 00000000000..9c1a915a638 --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/account/GateioFuturesFeeTest.java @@ -0,0 +1,34 @@ +package org.knowm.xchange.gateio.dto.account; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Map; + +import org.junit.Test; + +public class GateioFuturesFeeTest { + + @Test + public void testUnmarshal() throws IOException { + InputStream is = + GateioFuturesFeeTest.class.getResourceAsStream( + "/__files/api_v4_futures_fee.json"); + + ObjectMapper mapper = new ObjectMapper(); + Map fees = + mapper.readValue(is, new TypeReference>() { + }); + + assertThat(fees).hasSize(2); + assertThat(fees.get("1INCH_USDT").getTakerFee()).isEqualTo(new BigDecimal("0.00025")); + assertThat(fees.get("1INCH_USDT").getMakerFee()).isEqualTo(new BigDecimal("-0.00010")); + assertThat(fees.get("AAVE_USDT").getTakerFee()).isEqualTo(new BigDecimal("0.00025")); + assertThat(fees.get("AAVE_USDT").getMakerFee()).isEqualTo(new BigDecimal("-0.00010")); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/account/GateioSpotFeeTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/account/GateioSpotFeeTest.java new file mode 100644 index 00000000000..926f1df1a08 --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/account/GateioSpotFeeTest.java @@ -0,0 +1,39 @@ +package org.knowm.xchange.gateio.dto.account; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; + +import org.junit.Test; + +public class GateioSpotFeeTest { + + @Test + public void testUnmarshal() throws IOException { + InputStream is = getClass().getResourceAsStream("/__files/api_v4_spot_fee.json"); + ObjectMapper mapper = new ObjectMapper(); + GateioSpotFee gateioSpotFee = mapper.readValue(is, GateioSpotFee.class); + + assertThat(gateioSpotFee.getUserId()).isEqualTo(10001L); + assertThat(gateioSpotFee.getTakerFee()).isEqualTo(new BigDecimal("0.002")); + assertThat(gateioSpotFee.getMakerFee()).isEqualTo(new BigDecimal("0.002")); + assertThat(gateioSpotFee.getRpiMakerFee()).isEqualTo(new BigDecimal("-0.00175")); + assertThat(gateioSpotFee.getGtDiscount()).isFalse(); + assertThat(gateioSpotFee.getGtTakerFee()).isEqualTo(new BigDecimal("0")); + assertThat(gateioSpotFee.getGtMakerFee()).isEqualTo(new BigDecimal("0")); + assertThat(gateioSpotFee.getLoanFee()).isEqualTo(new BigDecimal("0.18")); + assertThat(gateioSpotFee.getPointType()).isEqualTo("1"); + assertThat(gateioSpotFee.getFuturesTakerFee()).isEqualTo(new BigDecimal("-0.00025")); + assertThat(gateioSpotFee.getFuturesMakerFee()).isEqualTo(new BigDecimal("0.00075")); + assertThat(gateioSpotFee.getFuturesRpiMakerFee()).isEqualTo(new BigDecimal("-0.00175")); + assertThat(gateioSpotFee.getDeliveryTakerFee()).isEqualTo(new BigDecimal("0.00016")); + assertThat(gateioSpotFee.getDeliveryMakerFee()).isEqualTo(new BigDecimal("-0.00015")); + assertThat(gateioSpotFee.getDebitFee()).isEqualTo(3); + assertThat(gateioSpotFee.getRpiMm()).isEqualTo(2); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesTickerDeserializationTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesTickerDeserializationTest.java new file mode 100644 index 00000000000..f643de07c05 --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/dto/marketdata/GateioFuturesTickerDeserializationTest.java @@ -0,0 +1,41 @@ +package org.knowm.xchange.gateio.dto.marketdata; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.gateio.GateioAdapters; +import org.knowm.xchange.utils.ObjectMapperHelper; + +public class GateioFuturesTickerDeserializationTest { + + @Test + public void testDeserialize() throws IOException { + InputStream is = getClass().getResourceAsStream("/__files/api_v4_futures_ticker.json"); + GateioFuturesTicker[] tickers = new ObjectMapper() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .readValue(is, GateioFuturesTicker[].class); + assertThat(tickers).isNotEmpty(); + GateioFuturesTicker ticker = tickers[0]; + + assertThat(ticker.getContract()).isEqualTo("BTC_USDT"); + assertThat(ticker.getLastPrice()).isEqualTo(new BigDecimal("6432")); + assertThat(ticker.getLow24h()).isEqualTo(new BigDecimal("6278")); + assertThat(ticker.getHigh24h()).isEqualTo(new BigDecimal("6790")); + assertThat(ticker.getChangeUtc0()).isNull(); + assertThat(ticker.getChangeUtc8()).isNull(); + assertThat(ticker.getQuantoBaseRate()).isNull(); + + // Check adapter mapping + Ticker adaptedTicker = GateioAdapters.toTickerFutures(ticker, BigDecimal.ONE); + assertThat(adaptedTicker.getHigh()).isEqualTo(new BigDecimal("6790")); + assertThat(adaptedTicker.getLow()).isEqualTo(new BigDecimal("6278")); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/examples/GateioFuturesTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/examples/GateioFuturesTest.java new file mode 100644 index 00000000000..8c97ba7a481 --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/examples/GateioFuturesTest.java @@ -0,0 +1,124 @@ +package org.knowm.xchange.gateio.examples; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.account.Fee; +import org.knowm.xchange.dto.marketdata.CandleStickData; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.gateio.GateioExchange; +import org.knowm.xchange.gateio.dto.trade.GateioCancelOrderParams; +import org.knowm.xchange.gateio.dto.trade.GateioOrderFlags; +import org.knowm.xchange.gateio.dto.trade.GateioTimeInForce; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.service.trade.params.CandleStickDataParams; +import org.knowm.xchange.service.trade.params.DefaultCandleStickParamWithLimit; +import org.knowm.xchange.utils.AuthUtils; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.knowm.xchange.gateio.GateioExchange.EXCHANGE_TYPE; +import static org.knowm.xchange.gateio.dto.GateioExchangeType.FUTURES; + +public class GateioFuturesTest { + private final Instrument instrument = new FuturesContract("ETH/USDT/PERP"); + public Exchange exchange; + private final boolean logOutput = false; + + @Before + public void before() { + init(); + } + + + @Test + @Ignore + public void order() throws IOException { + MarketOrder marketOrder = new MarketOrder(Order.OrderType.ASK, new BigDecimal("0.001"), instrument); + Ticker ticker = exchange.getMarketDataService().getTicker(instrument); + String userReference = "t-" + String.valueOf(System.currentTimeMillis()); + LimitOrder limitOrder = new LimitOrder.Builder(Order.OrderType.BID, instrument).limitPrice(ticker.getLow()).originalAmount(new BigDecimal("0.001")) + .userReference(userReference).build(); + limitOrder.addOrderFlag(new GateioOrderFlags(GateioTimeInForce.POC)); + String marketOrderId = exchange.getTradeService().placeMarketOrder(marketOrder); + String limitOrderId = exchange.getTradeService().placeLimitOrder(limitOrder); + LimitOrder limitOrder1 = new LimitOrder.Builder(Order.OrderType.BID, instrument).limitPrice(ticker.getLow().add(BigDecimal.ONE)).originalAmount(new BigDecimal("0.001")) + .userReference(userReference).build(); + exchange.getTradeService().changeOrder(limitOrder1); +// DefaultCancelOrderByInstrumentAndIdParams params = new DefaultCancelOrderByInstrumentAndIdParams(instrument, limitOrderId); + GateioCancelOrderParams params = new GateioCancelOrderParams(null, instrument, userReference); + exchange.getTradeService().cancelOrder(params); + } + + @Test + @Ignore + public void candleStick() throws IOException { + CandleStickDataParams params = new DefaultCandleStickParamWithLimit(new Date(System.currentTimeMillis() - 86400000 * 4), new Date(), 86400, 2); + CandleStickData candleStickData = exchange.getMarketDataService().getCandleStickData(instrument, params); + assertThat(candleStickData).isNotNull(); + assertThat(candleStickData.getCandleSticks()).isNotEmpty(); + if (logOutput) + candleStickData.getCandleSticks().forEach(System.out::println); + } + + @Test + @Ignore + public void getTicker() throws IOException { + Ticker ticker = exchange.getMarketDataService().getTicker(instrument); + assertThat(ticker).isNotNull(); + assertThat(ticker.getInstrument()).isEqualTo(instrument); + assertThat(ticker.getLast()).isGreaterThan(java.math.BigDecimal.ZERO); + } + + @Test + @Ignore + public void getTickers() throws IOException { + List tickers = exchange.getMarketDataService().getTickers(null); + assertThat(tickers).isNotEmpty(); + assertThat(tickers).allSatisfy(ticker -> { + assertThat(ticker.getInstrument()).isNotNull(); + assertThat(ticker.getLast()).isNotNull(); + }); + } + + @Test + @Ignore + public void setLeverage() throws IOException { + exchange.getAccountService().setLeverage(instrument, 1); + } + + @Test + @Ignore + public void getFees() throws IOException { + Map fees = exchange.getAccountService().getDynamicTradingFeesByInstrument("FUTURES"); + assertThat(fees).isNotNull(); + fees.forEach((instrument, fee) -> { + assertThat(instrument).isNotNull(); + assertThat(fee).isNotNull(); + assertThat(fee.getMakerFee()).isNotNull(); + assertThat(fee.getTakerFee()).isNotNull(); + if (logOutput) + System.out.println("Instrument: " + instrument + ", Maker Fee: " + fee.getMakerFee() + ", Taker Fee: " + fee.getTakerFee()); + }); + } + + private void init() { + ExchangeSpecification exchangeSpecification = + new ExchangeSpecification(GateioExchange.class); + exchangeSpecification.setExchangeSpecificParametersItem(EXCHANGE_TYPE, FUTURES); + AuthUtils.setApiAndSecretKey(exchangeSpecification, "gateio-main"); + exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/examples/GateioSpotTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/examples/GateioSpotTest.java new file mode 100644 index 00000000000..c3d467745cd --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/examples/GateioSpotTest.java @@ -0,0 +1,105 @@ +package org.knowm.xchange.gateio.examples; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.account.Fee; +import org.knowm.xchange.dto.marketdata.CandleStickData; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.gateio.GateioExchange; +import org.knowm.xchange.gateio.dto.trade.GateioOrderFlags; +import org.knowm.xchange.gateio.dto.trade.GateioTimeInForce; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.service.trade.params.CandleStickDataParams; +import org.knowm.xchange.service.trade.params.DefaultCandleStickParam; +import org.knowm.xchange.utils.AuthUtils; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GateioSpotTest { + private final Instrument currencyPair = CurrencyPair.ETH_USDT; + public Exchange exchange; + private final boolean logOutput = false; + + @Before + public void before() throws InterruptedException { + init(); + } + + @Test + @Ignore + public void placeOrder() throws IOException { + //amount: Trading quantity When type is limit, it refers to the base currency (the currency being traded), such as BTC in BTC_USDT When type is market, it refers to different currencies based on the side: + // side: buy refers to quote currency, BTC_USDT means USDT + // side: sell refers to base currency, BTC_USDT means BTC + MarketOrder marketOrder = new MarketOrder(Order.OrderType.BID, new BigDecimal("5"), currencyPair); + Ticker ticker = exchange.getMarketDataService().getTicker(currencyPair); + LimitOrder limitOrder = new LimitOrder.Builder(Order.OrderType.BID, currencyPair).limitPrice(ticker.getLow()).originalAmount(new BigDecimal("0.002")) + .userReference(String.valueOf(System.currentTimeMillis())).build(); + limitOrder.addOrderFlag(new GateioOrderFlags(GateioTimeInForce.POC)); + String marketOrderId = exchange.getTradeService().placeMarketOrder(marketOrder); + String limitOrderId = exchange.getTradeService().placeLimitOrder(limitOrder); + CandleStickDataParams params = new DefaultCandleStickParam(new Date(System.currentTimeMillis() - 86400000 * 4), new Date(), 86400); + CandleStickData candleStickData = exchange.getMarketDataService().getCandleStickData(currencyPair, params); + } + + @Test + @Ignore + public void getTicker() throws IOException { + Ticker ticker = exchange.getMarketDataService().getTicker(currencyPair); + assertThat(ticker).isNotNull(); + } + + @Test + @Ignore + public void getTickers() throws IOException { + List tickers = exchange.getMarketDataService().getTickers(null); + assertThat(tickers).isNotNull(); + assertThat(tickers.get(0)).isNotNull(); + } + + @Test + @Ignore + public void candleStick() throws IOException { + CandleStickDataParams params = new DefaultCandleStickParam(new Date(System.currentTimeMillis() - 86400000 * 4), new Date(), 86400); + CandleStickData candleStickData = exchange.getMarketDataService().getCandleStickData(currencyPair, params); + assertThat(candleStickData).isNotNull(); + assertThat(candleStickData.getCandleSticks()).isNotEmpty(); + if (logOutput) + candleStickData.getCandleSticks().forEach(System.out::println); + } + + @Test + @Ignore + public void getFees() throws IOException { + Map fees = exchange.getAccountService().getDynamicTradingFeesByInstrument("SPOT"); + assertThat(fees).isNotEmpty(); + fees.forEach((instrument, fee) -> { + assertThat(instrument).isNotNull(); + assertThat(fee.getMakerFee()).isNotNull(); + assertThat(fee.getTakerFee()).isNotNull(); + if (logOutput) + System.out.println("Instrument: " + instrument + ", Maker Fee: " + fee.getMakerFee() + ", Taker Fee: " + fee.getTakerFee()); + }); + } + + private void init() { + ExchangeSpecification exchangeSpecification = + new ExchangeSpecification(GateioExchange.class); + AuthUtils.setApiAndSecretKey(exchangeSpecification, "gateio-main"); + exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioAccountServiceTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioAccountServiceTest.java index 45a136f456d..d70bad7d8d2 100644 --- a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioAccountServiceTest.java +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioAccountServiceTest.java @@ -135,4 +135,14 @@ void funding_history() throws IOException { assertThat(actual).hasSize(2); assertThat(actual).first().usingRecursiveComparison().isEqualTo(expected); } + + @Test + void get_dynamic_trading_fees_by_instrument() throws IOException { + java.util.Map fees = + gateioAccountService.getDynamicTradingFeesByInstrument(); + assertThat(fees).isNotEmpty(); + org.knowm.xchange.dto.account.Fee fee = fees.values().iterator().next(); + assertThat(fee.getMakerFee()).isEqualTo(new java.math.BigDecimal("0.002")); + assertThat(fee.getTakerFee()).isEqualTo(new java.math.BigDecimal("0.002")); + } } diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesAccountServiceTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesAccountServiceTest.java new file mode 100644 index 00000000000..47adeb47251 --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesAccountServiceTest.java @@ -0,0 +1,65 @@ +package org.knowm.xchange.gateio.service; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.account.Fee; +import org.knowm.xchange.gateio.GateioExchange; +import org.knowm.xchange.gateio.config.Config; +import org.knowm.xchange.gateio.dto.GateioExchangeType; +import org.knowm.xchange.instrument.Instrument; +import si.mazi.rescu.CustomRestProxyFactoryImpl; + +import java.io.IOException; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GateioFuturesAccountServiceTest { + + static GateioExchange exchange; + static WireMockServer wireMockServer; + + @BeforeAll + static void init() { + wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort()); + wireMockServer.start(); + + Config.getInstance().setRestProxyFactoryClass(CustomRestProxyFactoryImpl.class); + + ExchangeSpecification exSpec = new ExchangeSpecification(GateioExchange.class); + exSpec.setSslUri("http://localhost:" + wireMockServer.port()); + exSpec.setApiKey("a"); + exSpec.setSecretKey("b"); + exSpec.setExchangeSpecificParametersItem(GateioExchange.EXCHANGE_TYPE, GateioExchangeType.FUTURES); + + exchange = (GateioExchange) ExchangeFactory.INSTANCE.createExchange(exSpec); + } + + @AfterAll + static void stop() { + wireMockServer.stop(); + } + + @Test + void get_dynamic_trading_fees_by_instrument() throws IOException { + GateioAccountService gateioAccountService = (GateioAccountService) exchange.getAccountService(); + Map fees = gateioAccountService.getDynamicTradingFeesByInstrument(); + assertThat(fees).isNotEmpty(); + Fee fee = fees.values().iterator().next(); + assertThat(fee.getMakerFee()).isNotNull(); + assertThat(fee.getTakerFee()).isNotNull(); + } + + @Test + void set_leverage() throws IOException { + GateioAccountService gateioAccountService = (GateioAccountService) exchange.getAccountService(); + boolean success = gateioAccountService.setLeverage(new FuturesContract("BTC/USDT/USDT"), 10); + assertThat(success).isTrue(); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesMarketDataServiceTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesMarketDataServiceTest.java new file mode 100644 index 00000000000..48a0c8ce37e --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesMarketDataServiceTest.java @@ -0,0 +1,75 @@ +package org.knowm.xchange.gateio.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.marketdata.CandleStickData; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.gateio.GateioExchange; +import org.knowm.xchange.gateio.GateioExchangeWiremock; +import org.knowm.xchange.gateio.dto.GateioExchangeType; +import org.knowm.xchange.gateio.dto.marketdata.GateioFundingRateHistory; +import org.knowm.xchange.service.trade.params.DefaultCandleStickParam; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GateioFuturesMarketDataServiceTest extends GateioExchangeWiremock { + + GateioMarketDataService gateioMarketDataService; + FuturesContract btcUsdt = new FuturesContract("BTC/USDT/PERP"); + + @BeforeEach + void setUp() throws IOException { + exchange.getExchangeSpecification().setExchangeSpecificParametersItem(GateioExchange.EXCHANGE_TYPE, GateioExchangeType.FUTURES); + exchange.remoteInit(); + gateioMarketDataService = (GateioMarketDataService) exchange.getMarketDataService(); + } + + @Test + void getTicker_valid_futures() throws IOException { + Ticker actual = gateioMarketDataService.getTicker(btcUsdt); + + assertThat(actual.getInstrument()).isEqualTo(btcUsdt); + assertThat(actual.getLast()).isEqualByComparingTo(new BigDecimal("6432")); + assertThat(actual.getAsk()).isEqualByComparingTo(new BigDecimal("34217.9")); + assertThat(actual.getAskSize()).isEqualByComparingTo(new BigDecimal("1000")); + assertThat(actual.getBid()).isEqualByComparingTo(new BigDecimal("34089.7")); + assertThat(actual.getBidSize()).isEqualByComparingTo(new BigDecimal("100")); + assertThat(actual.getHigh()).isEqualByComparingTo(new BigDecimal("6790")); + assertThat(actual.getLow()).isEqualByComparingTo(new BigDecimal("6278")); + assertThat(actual.getVolume()).isEqualByComparingTo(new BigDecimal("184040233284").multiply(new BigDecimal("0.0001"))); + assertThat(actual.getQuoteVolume()).isEqualByComparingTo(new BigDecimal("184040233284")); + assertThat(actual.getPercentageChange()).isEqualByComparingTo(new BigDecimal("4.43")); + } + + @Test + void getCandleStickData_valid_futures() throws IOException { + CandleStickData actual = gateioMarketDataService.getCandleStickData(btcUsdt, new DefaultCandleStickParam(null, null, 3600)); + + assertThat(actual.getInstrument()).isEqualTo(btcUsdt); + assertThat(actual.getCandleSticks()).hasSize(1); + assertThat(actual.getCandleSticks().get(0).getOpen()).isEqualTo(new BigDecimal("100")); + assertThat(actual.getCandleSticks().get(0).getHigh()).isEqualTo(new BigDecimal("110")); + assertThat(actual.getCandleSticks().get(0).getLow()).isEqualTo(new BigDecimal("90")); + assertThat(actual.getCandleSticks().get(0).getClose()).isEqualTo(new BigDecimal("105")); + assertThat(actual.getCandleSticks().get(0).getVolume()).isEqualByComparingTo(new BigDecimal("0.001")); + assertThat(actual.getCandleSticks().get(0).getTimestamp()).isEqualTo(Instant.ofEpochSecond(1600000000L)); + } + + @Test + void getFundingRateHistory_valid() throws IOException { + List actual = gateioMarketDataService.getFundingRateHistory( + btcUsdt, null, null, null); + + assertThat(actual).hasSize(2); + assertThat(actual.get(0).getRate()).isEqualTo("0.0001"); + assertThat(actual.get(0).getTimestamp()).isEqualTo(1684100000L); + assertThat(actual.get(1).getRate()).isEqualTo("0.0002"); + assertThat(actual.get(1).getTimestamp()).isEqualTo(1684103600L); + } +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesTradeServiceTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesTradeServiceTest.java new file mode 100644 index 00000000000..c4bf50d903b --- /dev/null +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioFuturesTradeServiceTest.java @@ -0,0 +1,100 @@ +package org.knowm.xchange.gateio.service; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.meta.ExchangeMetaData; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.gateio.GateioExchange; +import org.knowm.xchange.gateio.GateioExchangeWiremock; +import org.knowm.xchange.gateio.dto.GateioExchangeType; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.service.trade.params.DefaultCancelOrderByInstrumentAndIdParams; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GateioFuturesTradeServiceTest extends GateioExchangeWiremock { + + static GateioTradeService gateioTradeService; + static Instrument btcUsdtPerp = new FuturesContract("BTC/USDT/PERP"); + + @BeforeAll + public static void setup() { + ExchangeSpecification exSpec = exchange.getExchangeSpecification(); + exSpec.setExchangeSpecificParametersItem(GateioExchange.EXCHANGE_TYPE, GateioExchangeType.FUTURES); + + // Use a custom exchange class to allow setting metadata + TestGateioExchange testExchange = new TestGateioExchange(); + testExchange.applySpecification(exSpec); + exchange = testExchange; + gateioTradeService = (GateioTradeService) exchange.getTradeService(); + + // Mock metadata for contract value + Map instruments = new HashMap<>(); + instruments.put(btcUsdtPerp, InstrumentMetaData.builder() + .contractValue(new BigDecimal("0.0001")) + .build()); + ((TestGateioExchange) exchange).setExchangeMetaData(new ExchangeMetaData(instruments, null, null, null, null)); + } + + + @Test + void place_futures_limit_order() throws IOException { + LimitOrder limitOrder = new LimitOrder.Builder(OrderType.BID, btcUsdtPerp) + .limitPrice(new BigDecimal("50000")) + .originalAmount(BigDecimal.ONE) + .userReference("t-futures-limit-order") + .build(); + + String orderId = gateioTradeService.placeLimitOrder(limitOrder); + assertThat(orderId).isEqualTo("12345678"); + } + + + @Test + void place_futures_market_order() throws IOException { + MarketOrder marketOrder = new MarketOrder.Builder(OrderType.ASK, btcUsdtPerp) + .originalAmount(BigDecimal.ONE) + .userReference("t-futures-market-order") + .build(); + + String orderId = gateioTradeService.placeMarketOrder(marketOrder); + assertThat(orderId).isEqualTo("87654321"); + } + + @Test + void cancel_futures_order() throws IOException { + boolean cancelled = gateioTradeService.cancelOrder( + new DefaultCancelOrderByInstrumentAndIdParams(btcUsdtPerp, "15675394")); + + assertThat(cancelled).isTrue(); + } + + @Test + void change_futures_order() throws IOException { + LimitOrder limitOrder = new LimitOrder.Builder(OrderType.BID, btcUsdtPerp) + .id("15675394") + .originalAmount(BigDecimal.ONE) + .limitPrice(new BigDecimal("50000")) + .build(); + + String orderId = gateioTradeService.changeOrder(limitOrder); + assertThat(orderId).isEqualTo("15675394"); + } + + private static class TestGateioExchange extends GateioExchange { + public void setExchangeMetaData(ExchangeMetaData exchangeMetaData) { + this.exchangeMetaData = exchangeMetaData; + } + } + +} diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceRawTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceRawTest.java index d6a797e7e53..cafcfc798fa 100644 --- a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceRawTest.java +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceRawTest.java @@ -10,14 +10,15 @@ import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.gateio.GateioExchangeWiremock; -import org.knowm.xchange.gateio.dto.account.GateioOrder; +import org.knowm.xchange.gateio.dto.trade.GateioSpotOrderRequest; +import org.knowm.xchange.gateio.dto.trade.GateioSpotOrderResponse; class GateioTradeServiceRawTest extends GateioExchangeWiremock { GateioTradeServiceRaw gateioTradeServiceRaw = (GateioTradeServiceRaw) exchange.getTradeService(); - GateioOrder sampleMarketOrder = - GateioOrder.builder() + GateioSpotOrderResponse sampleMarketOrder = + GateioSpotOrderResponse.builder() .id("342251629898") .currencyPair(CurrencyPair.BTC_USDT) .clientOrderId("t-valid-market-buy-order") @@ -49,8 +50,8 @@ class GateioTradeServiceRawTest extends GateioExchangeWiremock { @Test void valid_market_buy_order() throws IOException { - GateioOrder gateioOrder = - GateioOrder.builder() + GateioSpotOrderRequest gateioOrder = + GateioSpotOrderRequest.builder() .currencyPair(CurrencyPair.BTC_USDT) .clientOrderId("t-valid-market-buy-order") .type("market") @@ -60,14 +61,14 @@ void valid_market_buy_order() throws IOException { .amount(BigDecimal.valueOf(20)) .build(); - GateioOrder actualResponse = gateioTradeServiceRaw.createOrder(gateioOrder); + GateioSpotOrderResponse actualResponse = gateioTradeServiceRaw.createOrder(gateioOrder); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(sampleMarketOrder); } @Test void valid_market_sell_order() throws IOException { - GateioOrder gateioOrder = - GateioOrder.builder() + GateioSpotOrderRequest gateioOrder = + GateioSpotOrderRequest.builder() .currencyPair(CurrencyPair.BTC_USDT) .clientOrderId("t-valid-market-sell-order") .type("market") @@ -77,10 +78,10 @@ void valid_market_sell_order() throws IOException { .amount(new BigDecimal("0.0007")) .build(); - GateioOrder actualResponse = gateioTradeServiceRaw.createOrder(gateioOrder); + GateioSpotOrderResponse actualResponse = gateioTradeServiceRaw.createOrder(gateioOrder); - GateioOrder expectedResponse = - GateioOrder.builder() + GateioSpotOrderResponse expectedResponse = + GateioSpotOrderResponse.builder() .id("342260949533") .currencyPair(CurrencyPair.BTC_USDT) .clientOrderId("t-valid-market-sell-order") @@ -115,7 +116,7 @@ void valid_market_sell_order() throws IOException { @Test void order_details() throws IOException { - GateioOrder actualResponse = + GateioSpotOrderResponse actualResponse = gateioTradeServiceRaw.getOrder("342251629898", CurrencyPair.BTC_USDT); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(sampleMarketOrder); diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceTest.java index d22290fce6c..a178c9a81f3 100644 --- a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceTest.java +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioTradeServiceTest.java @@ -1,24 +1,12 @@ package org.knowm.xchange.gateio.service; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -import java.io.IOException; -import java.math.BigDecimal; -import java.time.Instant; -import java.util.Collection; -import java.util.Date; import org.junit.jupiter.api.Test; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderStatus; import org.knowm.xchange.dto.Order.OrderType; -import org.knowm.xchange.dto.trade.LimitOrder; -import org.knowm.xchange.dto.trade.MarketOrder; -import org.knowm.xchange.dto.trade.OpenOrders; -import org.knowm.xchange.dto.trade.UserTrade; -import org.knowm.xchange.dto.trade.UserTrades; +import org.knowm.xchange.dto.trade.*; import org.knowm.xchange.exceptions.FundsExceededException; import org.knowm.xchange.gateio.GateioExchangeWiremock; import org.knowm.xchange.gateio.dto.trade.GateioUserTrade; @@ -28,6 +16,15 @@ import org.knowm.xchange.service.trade.params.orders.DefaultOpenOrdersParamInstrument; import org.knowm.xchange.service.trade.params.orders.DefaultQueryOrderParamInstrument; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Collection; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + class GateioTradeServiceTest extends GateioExchangeWiremock { GateioTradeService gateioTradeService = (GateioTradeService) exchange.getTradeService(); diff --git a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioV4DigestTest.java b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioV4DigestTest.java index 9f9594c7231..63dd4ecced4 100644 --- a/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioV4DigestTest.java +++ b/xchange-gateio-v4/src/test/java/org/knowm/xchange/gateio/service/GateioV4DigestTest.java @@ -1,16 +1,17 @@ package org.knowm.xchange.gateio.service; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import java.util.HashMap; -import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import si.mazi.rescu.RestInvocation; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) class GateioV4DigestTest { @@ -34,4 +35,23 @@ void signature() { assertThat(actual).isEqualTo(expected); } + + @Test + void signatureWithNullBody() { + GateioV4Digest gateioV4Digest = GateioV4Digest.createInstance("b"); + + when(restInvocation.getHttpMethod()).thenReturn("POST"); + when(restInvocation.getPath()).thenReturn("a"); + when(restInvocation.getQueryString()).thenReturn("?b=c"); + when(restInvocation.getRequestBody()).thenReturn(null); + Map headers = new HashMap<>(); + headers.put("Timestamp", "1691707273890"); + when(restInvocation.getHttpHeadersFromParams()).thenReturn(headers); + + String actual = gateioV4Digest.digestParams(restInvocation); + String expected = + "cc2c75d1db1a1fcf354f7bce55f3126c5a46829449e8f6c087b785d71b12aa142fee8fb9750f60a2cdea4fd84f8e7f1835a21a47d6709cc92b4ff757f7ccdbd1"; + + assertThat(actual).isEqualTo(expected); + } } diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_candlesticks.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_candlesticks.json new file mode 100644 index 00000000000..f7946494d61 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_candlesticks.json @@ -0,0 +1,11 @@ +[ + { + "t": 1600000000, + "v": 10, + "c": "105", + "h": "110", + "l": "90", + "o": "100", + "sum": "1000" + } +] diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_fee.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_fee.json new file mode 100644 index 00000000000..cbbd12ab20b --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_fee.json @@ -0,0 +1,10 @@ +{ + "1INCH_USDT": { + "taker_fee": "0.00025", + "maker_fee": "-0.00010" + }, + "AAVE_USDT": { + "taker_fee": "0.00025", + "maker_fee": "-0.00010" + } +} diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_funding_rate.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_funding_rate.json new file mode 100644 index 00000000000..5d46ad7e376 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_funding_rate.json @@ -0,0 +1,10 @@ +[ + { + "t": 1684100000, + "r": "0.0001" + }, + { + "t": 1684103600, + "r": "0.0002" + } +] diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_order_limit.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_order_limit.json new file mode 100644 index 00000000000..cbc7c4b32fc --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_order_limit.json @@ -0,0 +1,25 @@ +{ + "id": 12345678, + "create_time": 1600000000.123, + "finish_time": 0, + "finish_as": "open", + "status": "open", + "contract": "BTC_USDT", + "size": 1, + "iceberg": 0, + "price": "50000", + "close": false, + "is_close": false, + "reduce_only": false, + "is_reduce_only": false, + "type": "limit", + "tif": "gtc", + "left": 1, + "filled_total": 0, + "avg_deal_price": "0", + "fill_price": "0", + "text": "t-futures-limit-order", + "user": "10000", + "tkfr": "0.0005", + "mkfr": "0.0002" +} diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_order_market.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_order_market.json new file mode 100644 index 00000000000..073e44aaf10 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_order_market.json @@ -0,0 +1,25 @@ +{ + "id": 87654321, + "create_time": 1600000000.456, + "finish_time": 1600000000.789, + "finish_as": "filled", + "status": "finished", + "contract": "BTC_USDT", + "size": -1, + "iceberg": 0, + "price": "0", + "close": false, + "is_close": false, + "reduce_only": false, + "is_reduce_only": false, + "type": "market", + "tif": "ioc", + "left": 0, + "filled_total": 1, + "avg_deal_price": "50010.5", + "fill_price": "50010.5", + "text": "t-futures-market-order", + "user": "10000", + "tkfr": "0.0005", + "mkfr": "0.0002" +} diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_orders-amend-order.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_orders-amend-order.json new file mode 100644 index 00000000000..92881a67555 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_orders-amend-order.json @@ -0,0 +1,29 @@ +{ + "id": 15675394, + "user": 100000, + "contract": "BTC_USDT", + "create_time": 1546569968, + "size": "6024", + "iceberg": "0", + "left": "6024", + "price": "3765", + "fill_price": "0", + "mkfr": "-0.00025", + "tkfr": "0.00075", + "tif": "gtc", + "refu": 0, + "is_reduce_only": false, + "is_close": false, + "is_liq": false, + "text": "t-my-custom-id", + "status": "finished", + "finish_time": 1514764900, + "finish_as": "cancelled", + "stp_id": 0, + "stp_act": "-", + "amend_text": "-", + "order_value": "64112.2099000000005", + "trade_value": "64112.2099000000005", + "market_order_slip_ratio": "0.03", + "pos_margin_mode": "isolated" +} \ No newline at end of file diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_orders-valid-cancel-order.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_orders-valid-cancel-order.json new file mode 100644 index 00000000000..92881a67555 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_orders-valid-cancel-order.json @@ -0,0 +1,29 @@ +{ + "id": 15675394, + "user": 100000, + "contract": "BTC_USDT", + "create_time": 1546569968, + "size": "6024", + "iceberg": "0", + "left": "6024", + "price": "3765", + "fill_price": "0", + "mkfr": "-0.00025", + "tkfr": "0.00075", + "tif": "gtc", + "refu": 0, + "is_reduce_only": false, + "is_close": false, + "is_liq": false, + "text": "t-my-custom-id", + "status": "finished", + "finish_time": 1514764900, + "finish_as": "cancelled", + "stp_id": 0, + "stp_act": "-", + "amend_text": "-", + "order_value": "64112.2099000000005", + "trade_value": "64112.2099000000005", + "market_order_slip_ratio": "0.03", + "pos_margin_mode": "isolated" +} \ No newline at end of file diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_set_leverage.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_set_leverage.json new file mode 100644 index 00000000000..b114eb8c42f --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_set_leverage.json @@ -0,0 +1,4 @@ +{ + "leverage": "10.0", + "cross_leverage_limit": "20.0" +} diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_ticker.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_ticker.json new file mode 100644 index 00000000000..8a36b7e1645 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_futures_ticker.json @@ -0,0 +1,50 @@ +[ + { + "contract": "BTC_USDT", + "last": "6432", + "low_24h": "6278", + "high_24h": "6790", + "change_percentage": "4.43", + "total_size": "32323904", + "volume_24h": "184040233284", + "volume_24h_btc": "28613220", + "volume_24h_usd": "184040233284", + "volume_24h_base": "28613220", + "volume_24h_quote": "184040233284", + "volume_24h_settle": "28613220", + "mark_price": "6534", + "funding_rate": "0.0001", + "funding_rate_indicative": "0.0001", + "index_price": "6531", + "highest_bid": "34089.7", + "highest_size": "100", + "lowest_ask": "34217.9", + "lowest_size": "1000" + } +] +// +//{ +// "contract": "BTC_USDT", +// "last": "10000.5", +// "change_percentage": "1.5", +// "total_size": "100", +// "volume_24h": "1000", +// "volume_24h_btc": "0.1", +// "volume_24h_usd": "1000", +// "volume_24h_base": "0.1", +// "volume_24h_quote": "1000", +// "volume_24h_settle": "1000", +// "mark_price": "10001", +// "funding_rate": "0.0001", +// "funding_rate_indicative": "0.0001", +// "index_price": "10000", +// "lowest_ask": "10002", +// "highest_bid": "9999", +// "lowest_size": "1", +// "highest_size": "2", +// "low_24h": "9800", +// "high_24h": "10200", +// "change_utc0": "1.2", +// "change_utc8": "1.3", +// "quanto_base_rate": "0.05" +//} diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_instrument_details.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_instrument_details.json new file mode 100644 index 00000000000..d407978c589 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_instrument_details.json @@ -0,0 +1,55 @@ +[ + { + "name": "BTC_USDT", + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": false, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": "1", + "enable_decimal": false, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": "1000000", + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": "28530850594", + "position_size": "5223816", + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396, + "enable_bonus": true, + "enable_credit": true, + "create_time": 1669688556, + "funding_cap_ratio": "0.75", + "status": "trading", + "launch_time": 1609899548, + "delisting_time": 1609899548, + "delisted_time": 1609899548, + "market_order_slip_ratio": "0.05", + "market_order_size_max": "0", + "funding_rate_limit": "0.003", + "contract_type": "indices" + } +] diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_candlesticks.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_candlesticks.json new file mode 100644 index 00000000000..e242b6b5c43 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_candlesticks.json @@ -0,0 +1,12 @@ +[ + [ + "1600000000", + "10", + "105", + "110", + "90", + "100", + "1000", + "true" + ] +] diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_fee.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_fee.json new file mode 100644 index 00000000000..cbde72606ba --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_fee.json @@ -0,0 +1,18 @@ +{ + "user_id": 10001, + "taker_fee": "0.002", + "maker_fee": "0.002", + "rpi_maker_fee": "-0.00175", + "futures_taker_fee": "-0.00025", + "futures_maker_fee": "0.00075", + "futures_rpi_maker_fee": "-0.00175", + "gt_discount": false, + "gt_taker_fee": "0", + "gt_maker_fee": "0", + "loan_fee": "0.18", + "point_type": "1", + "delivery_taker_fee": "0.00016", + "delivery_maker_fee": "-0.00015", + "debit_fee": 3, + "rpi_mm": 2 +} \ No newline at end of file diff --git a/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_orders-amend-order.json b/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_orders-amend-order.json new file mode 100644 index 00000000000..8485cc25cb3 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/__files/api_v4_spot_orders-amend-order.json @@ -0,0 +1,33 @@ +{ + "id": "1852454420", + "text": "t-abc123", + "amend_text": "-", + "create_time": "1710488334", + "update_time": "1710488334", + "create_time_ms": 1710488334073, + "update_time_ms": 1710488334074, + "status": "closed", + "currency_pair": "BTC_USDT", + "type": "limit", + "account": "unified", + "side": "buy", + "amount": "0.001", + "price": "65000", + "time_in_force": "gtc", + "iceberg": "0", + "left": "0", + "filled_amount": "0.001", + "fill_price": "63.4693", + "filled_total": "63.4693", + "avg_deal_price": "63469.3", + "fee": "0.00000022", + "fee_currency": "BTC", + "point_fee": "0", + "gt_fee": "0", + "gt_maker_fee": "0", + "gt_taker_fee": "0", + "gt_discount": false, + "rebated_fee": "0", + "rebated_fee_currency": "USDT", + "finish_as": "filled" +} diff --git a/xchange-gateio-v4/src/test/resources/logback.xml b/xchange-gateio-v4/src/test/resources/logback.xml index c823ac40a99..0a2a5c7e0ef 100644 --- a/xchange-gateio-v4/src/test/resources/logback.xml +++ b/xchange-gateio-v4/src/test/resources/logback.xml @@ -17,7 +17,7 @@ - - + + diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_candlesticks_btc_usdt.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_candlesticks_btc_usdt.json new file mode 100644 index 00000000000..207c9ec9311 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_candlesticks_btc_usdt.json @@ -0,0 +1,21 @@ +{ + "request": { + "method": "GET", + "urlPath": "/api/v4/futures/usdt/candlesticks", + "queryParameters": { + "contract": { + "equalTo": "BTC_USDT" + }, + "interval": { + "equalTo": "1h" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_candlesticks.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_fee.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_fee.json new file mode 100644 index 00000000000..3e5a0d0bebe --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_fee.json @@ -0,0 +1,11 @@ +{ + "name": "api_v4_futures_fee", + "request": { + "url": "/api/v4/futures/usdt/fee", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_fee.json" + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_funding_rate.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_funding_rate.json new file mode 100644 index 00000000000..60ee5ea9d75 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_funding_rate.json @@ -0,0 +1,15 @@ +{ + "request": { + "urlPath": "/api/v4/futures/usdt/funding_rate", + "method": "GET", + "queryParameters": { + "contract": { + "equalTo": "BTC_USDT" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_funding_rate.json" + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_order_limit.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_order_limit.json new file mode 100644 index 00000000000..3b7b1aceff7 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_order_limit.json @@ -0,0 +1,24 @@ +{ + "request": { + "method": "POST", + "urlPath": "/api/v4/futures/usdt/orders", + "bodyPatterns": [ + { + "matchesJsonPath": "$[?(@.contract == 'BTC_USDT')]" + }, + { + "matchesJsonPath": "$[?(@.price == '50000')]" + }, + { + "matchesJsonPath": "$[?(@.text == 't-futures-limit-order')]" + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "api_v4_futures_order_limit.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_order_market.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_order_market.json new file mode 100644 index 00000000000..155cc2d2bea --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_order_market.json @@ -0,0 +1,27 @@ +{ + "request": { + "method": "POST", + "urlPath": "/api/v4/futures/usdt/orders", + "bodyPatterns": [ + { + "matchesJsonPath": "$[?(@.contract == 'BTC_USDT')]" + }, + { + "matchesJsonPath": "$[?(@.price == 0)]" + }, + { + "matchesJsonPath": "$[?(@.tif == 'ioc')]" + }, + { + "matchesJsonPath": "$[?(@.text == 't-futures-market-order')]" + } + ] + }, + "response": { + "status": 201, + "bodyFileName": "api_v4_futures_order_market.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_orders-amend-order.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_orders-amend-order.json new file mode 100644 index 00000000000..8686491a4b4 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_orders-amend-order.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "PUT", + "urlPath": "/api/v4/futures/usdt/orders/15675394" + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_orders-amend-order.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_orders-valid-cancel-order.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_orders-valid-cancel-order.json new file mode 100644 index 00000000000..83736083d4b --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_orders-valid-cancel-order.json @@ -0,0 +1,15 @@ +{ + "id": "f3a47141-86a1-432a-9e12-c2e365611234", + "name": "api_v4_futures_orders_12345678", + "request": { + "url": "/api/v4/futures/usdt/orders/15675394", + "method": "DELETE" + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_orders-valid-cancel-order.json" + }, + "uuid": "f3a47141-86a1-432a-9e12-c2e365611234", + "persistent": true, + "insertionIndex": 32 +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_set_leverage.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_set_leverage.json new file mode 100644 index 00000000000..47200b6c69a --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_set_leverage.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "POST", + "urlPattern": "/api/v4/futures/usdt/positions/BTC_USDT/leverage\\?leverage=10" + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_set_leverage.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_tickers_btc_usdt.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_tickers_btc_usdt.json new file mode 100644 index 00000000000..b7499d8136b --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_futures_tickers_btc_usdt.json @@ -0,0 +1,18 @@ +{ + "request": { + "method": "GET", + "urlPath": "/api/v4/futures/usdt/tickers", + "queryParameters": { + "contract": { + "equalTo": "BTC_USDT" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_futures_ticker.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_instrument_details.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_instrument_details.json new file mode 100644 index 00000000000..1e6e85a929f --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_instrument_details.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "urlPath": "/api/v4/futures/usdt/contracts" + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_instrument_details.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-amend-order.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-amend-order.json new file mode 100644 index 00000000000..fb9535e66f6 --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-amend-order.json @@ -0,0 +1,18 @@ +{ + "request": { + "method": "PATCH", + "urlPath": "/api/v4/spot/orders/1852454420", + "queryParameters": { + "currency_pair": { + "equalTo": "BTC_USDT" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_spot_orders-amend-order.json", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-buy-order.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-buy-order.json index 656848548e2..cdde7a36147 100644 --- a/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-buy-order.json +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-buy-order.json @@ -5,7 +5,7 @@ "url" : "/api/v4/spot/orders", "method" : "POST", "bodyPatterns" : [ { - "equalToJson" : "{\"id\":null,\"text\":\"t-valid-limit-buy-order\",\"amend_text\":null,\"create_time_ms\":null,\"update_time_ms\":null,\"status\":null,\"currency_pair\":\"BTC_USDT\",\"type\":\"limit\",\"account\":\"spot\",\"side\":\"buy\",\"amount\":0.00068,\"price\":10000.7,\"time_in_force\":\"gtc\",\"iceberg\":null,\"auto_borrow\":null,\"auto_repay\":null,\"left\":null,\"filled_total\":null,\"avg_deal_price\":null,\"fee\":null,\"fee_currency\":null,\"point_fee\":null,\"gt_fee\":null,\"gt_maker_fee\":null,\"gt_taker_fee\":null,\"gt_discount\":null,\"rebated_fee\":null,\"rebated_fee_currency\":null,\"stp_id\":null,\"stp_act\":null,\"finish_as\":null}", + "equalToJson": "{\"text\":\"t-valid-limit-buy-order\",\"currency_pair\":\"BTC_USDT\",\"type\":\"limit\",\"account\":\"spot\",\"side\":\"buy\",\"amount\":0.00068,\"price\":10000.7,\"time_in_force\":\"gtc\"}", "ignoreArrayOrder" : true, "ignoreExtraElements" : true } ] diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-sell-order.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-sell-order.json index ec557a33834..c61887d2ced 100644 --- a/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-sell-order.json +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_spot_orders-valid-limit-sell-order.json @@ -5,7 +5,7 @@ "url" : "/api/v4/spot/orders", "method" : "POST", "bodyPatterns" : [ { - "equalToJson" : "{\"id\":null,\"text\":\"t-valid-limit-sell-order\",\"amend_text\":null,\"create_time_ms\":null,\"update_time_ms\":null,\"status\":null,\"currency_pair\":\"BTC_USDT\",\"type\":\"limit\",\"account\":\"spot\",\"side\":\"sell\",\"amount\":0.00068,\"price\":29240.7,\"time_in_force\":\"gtc\",\"iceberg\":null,\"auto_borrow\":null,\"auto_repay\":null,\"left\":null,\"filled_total\":null,\"avg_deal_price\":null,\"fee\":null,\"fee_currency\":null,\"point_fee\":null,\"gt_fee\":null,\"gt_maker_fee\":null,\"gt_taker_fee\":null,\"gt_discount\":null,\"rebated_fee\":null,\"rebated_fee_currency\":null,\"stp_id\":null,\"stp_act\":null,\"finish_as\":null}", + "equalToJson": "{\"text\":\"t-valid-limit-sell-order\",\"currency_pair\":\"BTC_USDT\",\"type\":\"limit\",\"account\":\"spot\",\"side\":\"sell\",\"amount\":0.00068,\"price\":29240.7,\"time_in_force\":\"gtc\"}", "ignoreArrayOrder" : true, "ignoreExtraElements" : true } ] diff --git a/xchange-gateio-v4/src/test/resources/mappings/api_v4_wallet_fee.json b/xchange-gateio-v4/src/test/resources/mappings/api_v4_wallet_fee.json new file mode 100644 index 00000000000..9a45887c7ea --- /dev/null +++ b/xchange-gateio-v4/src/test/resources/mappings/api_v4_wallet_fee.json @@ -0,0 +1,11 @@ +{ + "name": "api_v4_wallet_fee", + "request": { + "url": "/api/v4/wallet/fee", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "api_v4_spot_fee.json" + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingAdapters.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingAdapters.java index 6530b83ebde..4009675644c 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingAdapters.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingAdapters.java @@ -2,17 +2,20 @@ import info.bitrich.xchangestream.gateio.dto.response.balance.BalancePayload; import info.bitrich.xchangestream.gateio.dto.response.balance.GateioSingleSpotBalanceNotification; +import info.bitrich.xchangestream.gateio.dto.response.order.GateioSingleOrderNotification; +import info.bitrich.xchangestream.gateio.dto.response.orderbook.GateioOrderBookFuturesNotification; import info.bitrich.xchangestream.gateio.dto.response.orderbook.GateioOrderBookNotification; import info.bitrich.xchangestream.gateio.dto.response.orderbook.OrderBookPayload; +import info.bitrich.xchangestream.gateio.dto.response.orderbook.OrderBookV2FuturesResponse; import info.bitrich.xchangestream.gateio.dto.response.ticker.GateioTickerNotification; import info.bitrich.xchangestream.gateio.dto.response.ticker.TickerPayload; import info.bitrich.xchangestream.gateio.dto.response.trade.GateioTradeNotification; +import info.bitrich.xchangestream.gateio.dto.response.trade.TradeFuturesPayload; import info.bitrich.xchangestream.gateio.dto.response.trade.TradePayload; import info.bitrich.xchangestream.gateio.dto.response.usertrade.GateioSingleUserTradeNotification; import info.bitrich.xchangestream.gateio.dto.response.usertrade.UserTradePayload; -import java.util.Date; -import java.util.stream.Stream; import lombok.experimental.UtilityClass; +import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.dto.marketdata.OrderBook; @@ -20,6 +23,10 @@ import org.knowm.xchange.dto.marketdata.Trade; import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.trade.UserTrade; +import org.knowm.xchange.gateio.GateioAdapters; + +import java.util.Date; +import java.util.stream.Stream; @UtilityClass public class GateioStreamingAdapters { @@ -42,8 +49,10 @@ public Ticker toTicker(GateioTickerNotification notification) { } public Trade toTrade(GateioTradeNotification notification) { - TradePayload tradePayload = notification.getResult(); + return toTrade(notification.getResult()); + } + public Trade toTrade(TradePayload tradePayload) { return Trade.builder() .type(tradePayload.getSide()) .originalAmount(tradePayload.getAmount()) @@ -54,6 +63,16 @@ public Trade toTrade(GateioTradeNotification notification) { .build(); } + public static Trade toTradeFutures(TradeFuturesPayload payload) { + return Trade.builder() + .originalAmount(payload.getSize().abs()) + .price(payload.getPrice()) + .timestamp(Date.from(payload.getTimeMs())) + .id(String.valueOf(payload.getId())) + .type(payload.getSize().signum() < 0 ? OrderType.ASK : OrderType.BID) + .build(); + } + public UserTrade toUserTrade(GateioSingleUserTradeNotification notification) { UserTradePayload userTradePayload = notification.getResult(); @@ -71,6 +90,10 @@ public UserTrade toUserTrade(GateioSingleUserTradeNotification notification) { .build(); } + public Order toOrder(GateioSingleOrderNotification notification) { + return GateioAdapters.toOrder(notification.getResult()); + } + public Balance toBalance(GateioSingleSpotBalanceNotification notification) { BalancePayload balancePayload = notification.getResult(); @@ -112,4 +135,36 @@ public OrderBook toOrderBook(GateioOrderBookNotification notification) { return new OrderBook(Date.from(orderBookPayload.getTimestamp()), asks, bids); } + + public OrderBook toOrderBookFutures(GateioOrderBookFuturesNotification notification) { + OrderBookV2FuturesResponse orderBookPayload = notification.getResult(); + + Stream asks = + orderBookPayload.getAsks().stream() + .map( + priceSizeEntry -> + new LimitOrder( + OrderType.ASK, + priceSizeEntry.getSize(), + orderBookPayload.getCurrencyPair(), + null, + null, + priceSizeEntry.getPrice())); + + Stream bids = + orderBookPayload.getBids().stream() + .map( + priceSizeEntry -> + new LimitOrder( + OrderType.BID, + priceSizeEntry.getSize(), + orderBookPayload.getCurrencyPair(), + null, + null, + priceSizeEntry.getPrice())); + + return new OrderBook(Date.from(orderBookPayload.getTimestamp()), asks, bids); + } + + } diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingExchange.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingExchange.java index c518244ea64..0eff26348bb 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingExchange.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingExchange.java @@ -1,10 +1,6 @@ package info.bitrich.xchangestream.gateio; -import info.bitrich.xchangestream.core.ProductSubscription; -import info.bitrich.xchangestream.core.StreamingAccountService; -import info.bitrich.xchangestream.core.StreamingExchange; -import info.bitrich.xchangestream.core.StreamingMarketDataService; -import info.bitrich.xchangestream.core.StreamingTradeService; +import info.bitrich.xchangestream.core.*; import info.bitrich.xchangestream.gateio.config.Config; import io.reactivex.rxjava3.core.Completable; import org.knowm.xchange.ExchangeSpecification; @@ -17,7 +13,17 @@ public class GateioStreamingExchange extends GateioExchange implements Streaming private StreamingTradeService streamingTradeService; private StreamingAccountService streamingAccountService; - public GateioStreamingExchange() {} + public GateioStreamingExchange() { + } + + @Override + protected void initServices() { + super.initServices(); + if (isFuturesEnabled()) + exchangeSpecification.setSslUri(Config.V4_FUTURES_URL); + else + exchangeSpecification.setSslUri(Config.V4_URL); + } @Override public Completable connect(ProductSubscription... args) { @@ -73,7 +79,7 @@ public void useCompressedMessages(boolean compressedMessages) { public ExchangeSpecification getDefaultExchangeSpecification() { ExchangeSpecification specification = super.getDefaultExchangeSpecification(); specification.setShouldLoadRemoteMetaData(false); - specification.setSslUri(Config.V4_URL); + return specification; } } diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataService.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataService.java index 06c0df09bfe..04a8aed9b52 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataService.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataService.java @@ -2,27 +2,68 @@ import info.bitrich.xchangestream.core.StreamingMarketDataService; import info.bitrich.xchangestream.gateio.config.Config; +import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; +import info.bitrich.xchangestream.gateio.dto.response.orderbook.GateioOrderBookFuturesNotification; import info.bitrich.xchangestream.gateio.dto.response.orderbook.GateioOrderBookNotification; +import info.bitrich.xchangestream.gateio.dto.response.orderbook.OrderBookV2FuturesResponse; import info.bitrich.xchangestream.gateio.dto.response.ticker.GateioTickerNotification; +import info.bitrich.xchangestream.gateio.dto.response.trade.GateioFuturesTradeNotification; import info.bitrich.xchangestream.gateio.dto.response.trade.GateioTradeNotification; import io.reactivex.rxjava3.core.Observable; -import java.time.Duration; import org.apache.commons.lang3.ArrayUtils; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.marketdata.Trade; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.instrument.Instrument; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.*; public class GateioStreamingMarketDataService implements StreamingMarketDataService { public static final int MAX_DEPTH_DEFAULT = 5; - public static final int UPDATE_INTERVAL_DEFAULT = 100; + public static final Duration UPDATE_INTERVAL_DEFAULT = Duration.ofMillis(100); private final GateioStreamingService service; public GateioStreamingMarketDataService(GateioStreamingService service) { this.service = service; } + @Override + public Observable getOrderBook(Instrument instrument, Object... args) { + Integer orderBookLevel = (Integer) ArrayUtils.get(args, 0, MAX_DEPTH_DEFAULT); + + String channelName = + (instrument instanceof FuturesContract) + ? Config.FUTURES_ORDERBOOK_CHANNEL + : Config.SPOT_ORDERBOOK_CHANNEL; + +// String currencyPair = instrument.getCounter()+"/"+instrument.getBase(); + Observable updates = + service.subscribeChannel(channelName, instrument, orderBookLevel); + +// if (instrument instanceof FuturesContract) { +// return updates +// .map(GateioOrderBookFuturesNotification.class::cast) +// .scan(new FuturesOrderBookState((FuturesContract) instrument), FuturesOrderBookState::apply) +// .skip(1) +// .map(FuturesOrderBookState::toOrderBook); + + return updates + .map(GateioOrderBookFuturesNotification.class::cast) + .map(GateioStreamingAdapters::toOrderBookFutures); + } + + @Override + public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + return getOrderBook((Instrument) currencyPair, args); + } + /** * Uses the limited-level snapshot method: * https://www.gate.io/docs/apiv4/ws/index.html#limited-level-full-order-book-snapshot @@ -30,8 +71,7 @@ public GateioStreamingMarketDataService(GateioStreamingService service) { * @param currencyPair Currency pair of the order book * @param args Order book level: {@link Integer}, update speed: {@link Duration} */ - @Override - public Observable getOrderBook(CurrencyPair currencyPair, Object... args) { + public Observable getOrderBookLegacy(CurrencyPair currencyPair, Object... args) { Integer orderBookLevel = (Integer) ArrayUtils.get(args, 0, MAX_DEPTH_DEFAULT); Duration updateSpeed = (Duration) ArrayUtils.get(args, 1, UPDATE_INTERVAL_DEFAULT); return service @@ -49,6 +89,32 @@ public Observable getTicker(CurrencyPair currencyPair, Object... args) { .map(GateioStreamingAdapters::toTicker); } + @Override + public Observable getTrades(Instrument instrument, Object... args) { + if (instrument instanceof FuturesContract) { + return service + .subscribeChannel( + Config.FUTURES_TRADES_CHANNEL, ((FuturesContract) instrument).getCurrencyPair()) + .map(GateioFuturesTradeNotification.class::cast) + .flatMapIterable(GateioFuturesTradeNotification::getResult) + .map(payload -> { + Trade trade = GateioStreamingAdapters.toTradeFutures(payload); + return Trade.builder() + .type(trade.getType()) + .originalAmount(trade.getOriginalAmount()) + .instrument(instrument) + .price(trade.getPrice()) + .timestamp(trade.getTimestamp()) + .id(trade.getId()) + .build(); + }); + } + if (instrument instanceof CurrencyPair) { + return getTrades((CurrencyPair) instrument, args); + } + throw new IllegalArgumentException("Instrument type not supported: " + instrument.getClass()); + } + @Override public Observable getTrades(CurrencyPair currencyPair, Object... args) { return service @@ -56,4 +122,72 @@ public Observable getTrades(CurrencyPair currencyPair, Object... args) { .map(GateioTradeNotification.class::cast) .map(GateioStreamingAdapters::toTrade); } + + private static final class FuturesOrderBookState { + + private final FuturesContract instrument; + private final TreeMap asks; + private final TreeMap bids; + private Date timestamp; + + private FuturesOrderBookState(FuturesContract instrument) { + this(instrument, new TreeMap<>(), new TreeMap<>(Comparator.reverseOrder()), null); + } + + private FuturesOrderBookState( + FuturesContract instrument, + TreeMap asks, + TreeMap bids, + Date timestamp) { + this.instrument = instrument; + this.asks = asks; + this.bids = bids; + this.timestamp = timestamp; + } + + private FuturesOrderBookState apply(GateioOrderBookFuturesNotification notification) { + OrderBookV2FuturesResponse payload = notification.getResult(); + + if (payload.isFull()) { + asks.clear(); + bids.clear(); + } + + applySide(asks, payload.getAsks()); + applySide(bids, payload.getBids()); + timestamp = Date.from(payload.getTimestamp()); + return this; + } + + private void applySide( + Map book, List entries) { + if (entries == null) { + return; + } + + for (OrderBookV2FuturesResponse.PriceSizeEntry entry : entries) { + if (entry.getSize() == null || entry.getPrice() == null) { + continue; + } + if (entry.getSize().compareTo(BigDecimal.ZERO) == 0) { + book.remove(entry.getPrice()); + } else { + book.put(entry.getPrice(), entry.getSize()); + } + } + } + + private OrderBook toOrderBook() { + return new OrderBook(timestamp, toOrders(asks, OrderType.ASK), toOrders(bids, OrderType.BID)); + } + + private List toOrders(Map levels, OrderType type) { + List orders = new ArrayList<>(levels.size()); + for (Map.Entry entry : levels.entrySet()) { + orders.add( + new LimitOrder(type, entry.getValue(), instrument, null, null, entry.getKey())); + } + return orders; + } + } } diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingService.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingService.java index 02c0a91a53b..ffbd3e15ab0 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingService.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingService.java @@ -7,10 +7,7 @@ import info.bitrich.xchangestream.gateio.dto.Event; import info.bitrich.xchangestream.gateio.dto.request.GateioWsRequest; import info.bitrich.xchangestream.gateio.dto.request.GateioWsRequest.AuthInfo; -import info.bitrich.xchangestream.gateio.dto.request.payload.CurrencyPairLevelIntervalPayload; -import info.bitrich.xchangestream.gateio.dto.request.payload.CurrencyPairPayload; -import info.bitrich.xchangestream.gateio.dto.request.payload.EmptyPayload; -import info.bitrich.xchangestream.gateio.dto.request.payload.StringPayload; +import info.bitrich.xchangestream.gateio.dto.request.payload.*; import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; import info.bitrich.xchangestream.gateio.dto.response.balance.GateioMultipleSpotBalanceNotification; import info.bitrich.xchangestream.gateio.dto.response.usertrade.GateioMultipleUserTradeNotification; @@ -19,16 +16,19 @@ import info.bitrich.xchangestream.service.netty.WebSocketClientCompressionAllowClientNoContextAndServerNoContextHandler; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.reactivex.rxjava3.core.Observable; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.Validate; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.instrument.Instrument; + import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.Validate; -import org.knowm.xchange.currency.CurrencyPair; @Slf4j public class GateioStreamingService extends NettyStreamingService { @@ -53,15 +53,19 @@ public GateioStreamingService(String apiUri, String apiKey, String apiSecret) { @Override protected String getChannelNameFromMessage(GateioWsNotification message) { + return message.getUniqueChannelName(); } @Override public String getSubscriptionUniqueId(String channelName, Object... args) { - final CurrencyPair currencyPair = - (args.length > 0 && args[0] instanceof CurrencyPair) ? ((CurrencyPair) args[0]) : null; - - return String.format("%s%s%s", channelName, Config.CHANNEL_NAME_DELIMITER, currencyPair); + final Instrument instrument = + (args.length > 0 && args[0] instanceof Instrument) ? ((Instrument) args[0]) : null; + if (instrument instanceof FuturesContract) { + CurrencyPair currencyPair = new CurrencyPair(instrument.getBase(), instrument.getCounter()); + return String.format("%s%s%s", channelName, Config.CHANNEL_NAME_DELIMITER, currencyPair); + } else + return String.format("%s%s%s", channelName, Config.CHANNEL_NAME_DELIMITER, instrument); } @Override @@ -112,29 +116,48 @@ private GateioWsRequest getWsRequest(String channelName, Event event, Object... // channels require only currency pair in payload case Config.SPOT_TICKERS_CHANNEL: - case Config.SPOT_TRADES_CHANNEL: + case Config.SPOT_TRADES_CHANNEL: { + CurrencyPair instrument = (CurrencyPair) ArrayUtils.get(args, 0); + Objects.requireNonNull(instrument); + + payload = CurrencyPairPayload.builder().currencyPair(instrument).build(); + break; + } + case Config.FUTURES_USER_ORDERS_CHANNEL: + case Config.FUTURES_TRADES_CHANNEL: { - CurrencyPair currencyPair = (CurrencyPair) ArrayUtils.get(args, 0); - Objects.requireNonNull(currencyPair); + Instrument instrument = (Instrument) ArrayUtils.get(args, 0); + Objects.requireNonNull(instrument); - payload = CurrencyPairPayload.builder().currencyPair(currencyPair).build(); + payload = InstrumentPayload.builder().instrument(instrument).build(); break; } // channel requires currency pair, level, interval in payload - case Config.SPOT_ORDERBOOK_CHANNEL: - { - CurrencyPair currencyPair = (CurrencyPair) ArrayUtils.get(args, 0); - Integer orderBookLevel = (Integer) ArrayUtils.get(args, 1); - Duration updateSpeed = (Duration) ArrayUtils.get(args, 2); - Validate.noNullElements(new Object[] {currencyPair, orderBookLevel, updateSpeed}); - - payload = - CurrencyPairLevelIntervalPayload.builder() - .currencyPair(currencyPair) - .orderBookLevel(orderBookLevel) - .updateSpeed(updateSpeed) - .build(); + case Config.SPOT_ORDERBOOK_CHANNEL: { + CurrencyPair currencyPair = (CurrencyPair) ArrayUtils.get(args, 0); + Integer orderBookLevel = (Integer) ArrayUtils.get(args, 1); + Duration updateSpeed = (Duration) ArrayUtils.get(args, 2); + Validate.noNullElements(new Object[]{currencyPair, orderBookLevel, updateSpeed}); + + payload = + CurrencyPairLevelIntervalPayload.builder() + .currencyPair(currencyPair) + .orderBookLevel(orderBookLevel) + .updateSpeed(updateSpeed) + .build(); + break; + } + case Config.FUTURES_ORDERBOOK_CHANNEL: { + Instrument instrument = (Instrument) ArrayUtils.get(args, 0); + CurrencyPair currencyPair = new CurrencyPair(instrument.getBase(), instrument.getCounter()); + Integer orderBookLevel = (Integer) ArrayUtils.get(args, 1); + Validate.noNullElements(new Object[]{instrument, orderBookLevel}); + payload = OrderBookV2RequestPayload.builder() + .instrument(currencyPair) + .orderBookLevel(orderBookLevel) + .build(); + //payload = "ob." +instrument.getBase()+"_"+instrument.getCounter()+"."+orderBookLevel.toString(); break; } diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeService.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeService.java index 15664abdcbe..bbadfb3637f 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeService.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeService.java @@ -2,10 +2,16 @@ import info.bitrich.xchangestream.core.StreamingTradeService; import info.bitrich.xchangestream.gateio.config.Config; +import info.bitrich.xchangestream.gateio.dto.response.order.GateioSingleOrderFuturesNotification; +import info.bitrich.xchangestream.gateio.dto.response.order.GateioSingleOrderNotification; import info.bitrich.xchangestream.gateio.dto.response.usertrade.GateioSingleUserTradeNotification; import io.reactivex.rxjava3.core.Observable; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.trade.UserTrade; +import org.knowm.xchange.gateio.GateioAdapters; +import org.knowm.xchange.instrument.Instrument; public class GateioStreamingTradeService implements StreamingTradeService { @@ -27,4 +33,27 @@ public Observable getUserTrades(CurrencyPair currencyPair, Object... public Observable getUserTrades() { return getUserTrades(null); } + + @Override + public Observable getOrderChanges(Instrument instrument, Object... args) { + if (instrument instanceof CurrencyPair) { + return getOrderChanges((CurrencyPair) instrument, args); + } + if (instrument instanceof FuturesContract) { + return service + .subscribeChannel(Config.FUTURES_USER_ORDERS_CHANNEL, ((FuturesContract) instrument).getCurrencyPair()) + .map(GateioSingleOrderFuturesNotification.class::cast) + .flatMapIterable(GateioSingleOrderFuturesNotification::getResult) + .map(GateioAdapters::toOrder); + } + throw new IllegalArgumentException("Instrument type not supported: " + instrument.getClass()); + } + + @Override + public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) { + return service + .subscribeChannel(Config.SPOT_USER_ORDERS_CHANNEL, currencyPair) + .map(GateioSingleOrderNotification.class::cast) + .map(GateioStreamingAdapters::toOrder); + } } diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/config/Config.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/config/Config.java index c40bc2e1974..5c2af0eba30 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/config/Config.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/config/Config.java @@ -5,23 +5,28 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.Data; + import java.time.Clock; import java.util.Arrays; import java.util.List; -import lombok.Data; @Data public final class Config { public static final String V4_URL = "wss://api.gateio.ws/ws/v4/"; - + public static final String V4_FUTURES_URL = "wss://fx-ws.gateio.ws/v4/ws/usdt"; public static final String SPOT_ORDERBOOK_CHANNEL = "spot.order_book"; + public static final String FUTURES_ORDERBOOK_CHANNEL = "futures.obu"; public static final String SPOT_TRADES_CHANNEL = "spot.trades"; + public static final String FUTURES_TRADES_CHANNEL = "futures.trades"; public static final String SPOT_TICKERS_CHANNEL = "spot.tickers"; public static final String SPOT_BALANCES_CHANNEL = "spot.balances"; public static final String SPOT_USER_TRADES_CHANNEL = "spot.usertrades"; + public static final String SPOT_USER_ORDERS_CHANNEL = "spot.orders"; + public static final String FUTURES_USER_ORDERS_CHANNEL = "futures.orders"; public static final List PRIVATE_CHANNELS = - Arrays.asList(SPOT_BALANCES_CHANNEL, SPOT_USER_TRADES_CHANNEL); + Arrays.asList(SPOT_BALANCES_CHANNEL, SPOT_USER_TRADES_CHANNEL, SPOT_USER_ORDERS_CHANNEL, FUTURES_USER_ORDERS_CHANNEL); public static final String CHANNEL_NAME_DELIMITER = "-"; diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/request/payload/InstrumentPayload.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/request/payload/InstrumentPayload.java new file mode 100644 index 00000000000..7a49cb0c7b3 --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/request/payload/InstrumentPayload.java @@ -0,0 +1,16 @@ +package info.bitrich.xchangestream.gateio.dto.request.payload; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Data; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.gateio.config.converter.CurrencyPairToStringConverter; +import org.knowm.xchange.instrument.Instrument; + +@Data +@SuperBuilder +@Jacksonized +public class InstrumentPayload { + @JsonSerialize(converter = CurrencyPairToStringConverter.class) + private Instrument instrument; +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/request/payload/OrderBookV2RequestPayload.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/request/payload/OrderBookV2RequestPayload.java new file mode 100644 index 00000000000..232c59aaffc --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/request/payload/OrderBookV2RequestPayload.java @@ -0,0 +1,35 @@ +package info.bitrich.xchangestream.gateio.dto.request.payload; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.instrument.Instrument; + +@Data +@Builder +@Jacksonized +public class OrderBookV2RequestPayload { + + private Instrument instrument; + + private Integer orderBookLevel; + + @JsonValue + public String[] toPayload() { + CurrencyPair currencyPair = + instrument instanceof FuturesContract + ? ((FuturesContract) instrument).getCurrencyPair() + : (CurrencyPair) instrument; + + return new String[]{ + String.format( + "ob.%s_%s.%s", + currencyPair.getBase().getCurrencyCode(), + currencyPair.getCounter().getCurrencyCode(), + orderBookLevel) + }; + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/GateioWsNotification.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/GateioWsNotification.java index efad0a0f516..38d43d9cec0 100644 --- a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/GateioWsNotification.java +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/GateioWsNotification.java @@ -8,25 +8,38 @@ import info.bitrich.xchangestream.gateio.config.Config; import info.bitrich.xchangestream.gateio.dto.Event; import info.bitrich.xchangestream.gateio.dto.response.balance.GateioMultipleSpotBalanceNotification; +import info.bitrich.xchangestream.gateio.dto.response.order.GateioSingleOrderFuturesNotification; +import info.bitrich.xchangestream.gateio.dto.response.order.GateioSingleOrderNotification; +import info.bitrich.xchangestream.gateio.dto.response.orderbook.GateioOrderBookFuturesNotification; import info.bitrich.xchangestream.gateio.dto.response.orderbook.GateioOrderBookNotification; import info.bitrich.xchangestream.gateio.dto.response.ticker.GateioTickerNotification; +import info.bitrich.xchangestream.gateio.dto.response.trade.GateioFuturesTradeNotification; import info.bitrich.xchangestream.gateio.dto.response.trade.GateioTradeNotification; import info.bitrich.xchangestream.gateio.dto.response.usertrade.GateioMultipleUserTradeNotification; -import java.time.Instant; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; import org.knowm.xchange.gateio.config.converter.TimestampSecondsToInstantConverter; +import java.time.Instant; + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "channel", visible = true) @JsonSubTypes({ - @Type(value = GateioTradeNotification.class, name = Config.SPOT_TRADES_CHANNEL), - @Type(value = GateioTickerNotification.class, name = Config.SPOT_TICKERS_CHANNEL), - @Type(value = GateioOrderBookNotification.class, name = Config.SPOT_ORDERBOOK_CHANNEL), - @Type(value = GateioMultipleSpotBalanceNotification.class, name = Config.SPOT_BALANCES_CHANNEL), - @Type(value = GateioMultipleUserTradeNotification.class, name = Config.SPOT_USER_TRADES_CHANNEL) + @Type(value = GateioTradeNotification.class, name = Config.SPOT_TRADES_CHANNEL), + @Type(value = GateioFuturesTradeNotification.class, name = Config.FUTURES_TRADES_CHANNEL), + @Type(value = GateioTickerNotification.class, name = Config.SPOT_TICKERS_CHANNEL), + @Type(value = GateioOrderBookNotification.class, name = Config.SPOT_ORDERBOOK_CHANNEL), + @Type(value = GateioOrderBookFuturesNotification.class, name = Config.FUTURES_ORDERBOOK_CHANNEL), + @Type(value = GateioMultipleSpotBalanceNotification.class, name = Config.SPOT_BALANCES_CHANNEL), + @Type(value = GateioMultipleUserTradeNotification.class, name = Config.SPOT_USER_TRADES_CHANNEL), + @Type(value = GateioSingleOrderNotification.class, name = Config.SPOT_USER_ORDERS_CHANNEL), + @Type(value = GateioSingleOrderFuturesNotification.class, name = Config.FUTURES_USER_ORDERS_CHANNEL) }) @Data +@NoArgsConstructor +@AllArgsConstructor @SuperBuilder @Jacksonized public class GateioWsNotification { diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/order/GateioSingleOrderFuturesNotification.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/order/GateioSingleOrderFuturesNotification.java new file mode 100644 index 00000000000..b2dfd18ba46 --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/order/GateioSingleOrderFuturesNotification.java @@ -0,0 +1,28 @@ +package info.bitrich.xchangestream.gateio.dto.response.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import info.bitrich.xchangestream.gateio.config.Config; +import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; +import lombok.Data; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.gateio.dto.trade.GateioFuturesOrderResponse; + +import java.util.List; + +@Data +@SuperBuilder +@Jacksonized +public class GateioSingleOrderFuturesNotification extends GateioWsNotification { + @JsonProperty("result") + private List result; + + @Override + public String getUniqueChannelName() { + String suffix = + result.get(0).getContract() != null + ? Config.CHANNEL_NAME_DELIMITER + result.get(0).getContract() + : ""; + return super.getUniqueChannelName() + suffix; + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/order/GateioSingleOrderNotification.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/order/GateioSingleOrderNotification.java new file mode 100644 index 00000000000..32a5c3478f3 --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/order/GateioSingleOrderNotification.java @@ -0,0 +1,25 @@ +package info.bitrich.xchangestream.gateio.dto.response.order; + +import info.bitrich.xchangestream.gateio.config.Config; +import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; +import lombok.Data; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.gateio.dto.trade.GateioSpotOrderResponse; + +@Data +@SuperBuilder +@Jacksonized +public class GateioSingleOrderNotification extends GateioWsNotification { + + private GateioSpotOrderResponse result; + + @Override + public String getUniqueChannelName() { + String suffix = + result.getCurrencyPair() != null + ? Config.CHANNEL_NAME_DELIMITER + result.getCurrencyPair() + : ""; + return super.getUniqueChannelName() + suffix; + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/orderbook/GateioOrderBookFuturesNotification.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/orderbook/GateioOrderBookFuturesNotification.java new file mode 100644 index 00000000000..e679ae76a0d --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/orderbook/GateioOrderBookFuturesNotification.java @@ -0,0 +1,30 @@ +package info.bitrich.xchangestream.gateio.dto.response.orderbook; + +import com.fasterxml.jackson.annotation.JsonProperty; +import info.bitrich.xchangestream.gateio.config.Config; +import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@Jacksonized +public class GateioOrderBookFuturesNotification extends GateioWsNotification { + + @JsonProperty("result") + private OrderBookV2FuturesResponse result; + + @Override + public String getUniqueChannelName() { + String suffix = + result.getCurrencyPair() != null + ? Config.CHANNEL_NAME_DELIMITER + result.getCurrencyPair() + : ""; + return super.getUniqueChannelName() + suffix; + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/orderbook/OrderBookV2FuturesResponse.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/orderbook/OrderBookV2FuturesResponse.java new file mode 100644 index 00000000000..e22b4dbb36f --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/orderbook/OrderBookV2FuturesResponse.java @@ -0,0 +1,72 @@ +package info.bitrich.xchangestream.gateio.dto.response.orderbook; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.currency.CurrencyPair; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@Data +public class OrderBookV2FuturesResponse { + + @JsonProperty("t") + private Instant timestamp; + + @JsonProperty("full") + private boolean full; + + @JsonProperty("s") + private String streamName; + + @JsonProperty("U") + private Long firstUpdateId; + + @JsonProperty("u") + private Long lastUpdateId; + + @JsonProperty("asks") + @JsonAlias("a") + private List asks = new ArrayList<>(); + + @JsonProperty("bids") + @JsonAlias("b") + private List bids = new ArrayList<>(); + + @JsonIgnore + public CurrencyPair getCurrencyPair() { + if (streamName == null || !streamName.startsWith("ob.")) { + return null; + } + + String[] parts = streamName.substring(3).split("\\."); + if (parts.length < 2) { + return null; + } + + String[] currencies = parts[0].split("_"); + if (currencies.length != 2) { + return null; + } + + return new CurrencyPair(currencies[0], currencies[1]); + } + + @Data + @Builder + @Jacksonized + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + public static class PriceSizeEntry { + + BigDecimal price; + + BigDecimal size; + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/trade/GateioFuturesTradeNotification.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/trade/GateioFuturesTradeNotification.java new file mode 100644 index 00000000000..93e2d964981 --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/trade/GateioFuturesTradeNotification.java @@ -0,0 +1,28 @@ +package info.bitrich.xchangestream.gateio.dto.response.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; +import info.bitrich.xchangestream.gateio.config.Config; +import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; +import lombok.Data; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +import java.util.List; + +@Data +@SuperBuilder +@Jacksonized +public class GateioFuturesTradeNotification extends GateioWsNotification { + + @JsonProperty("result") + private List result; + + @Override + public String getUniqueChannelName() { + String suffix = + result.get(0).contract != null + ? Config.CHANNEL_NAME_DELIMITER + result.get(0).contract + : ""; + return super.getUniqueChannelName() + suffix; + } +} diff --git a/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/trade/TradeFuturesPayload.java b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/trade/TradeFuturesPayload.java new file mode 100644 index 00000000000..e04aceb2a69 --- /dev/null +++ b/xchange-stream-gateio/src/main/java/info/bitrich/xchangestream/gateio/dto/response/trade/TradeFuturesPayload.java @@ -0,0 +1,32 @@ +package info.bitrich.xchangestream.gateio.dto.response.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Data; +import org.knowm.xchange.gateio.config.converter.DoubleToInstantConverter; +import org.knowm.xchange.gateio.config.converter.StringToCurrencyPairConverter; +import org.knowm.xchange.gateio.config.converter.TimestampSecondsToInstantConverter; +import org.knowm.xchange.instrument.Instrument; + +import java.math.BigDecimal; +import java.time.Instant; + +@Data +public class TradeFuturesPayload { + @JsonProperty("contract") + @JsonDeserialize(converter = StringToCurrencyPairConverter.class) + Instrument contract; + @JsonProperty("size") + BigDecimal size; + @JsonProperty("id") + Long id; + @JsonProperty("create_time") + @JsonDeserialize(converter = TimestampSecondsToInstantConverter.class) + Instant time; + @JsonProperty("create_time_ms") + @JsonDeserialize(converter = DoubleToInstantConverter.class) + Instant timeMs; + @JsonProperty("price") + BigDecimal price; + boolean is_internal; +} diff --git a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioManualExample.java b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioManualExample.java deleted file mode 100644 index 1471fbff261..00000000000 --- a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioManualExample.java +++ /dev/null @@ -1,60 +0,0 @@ -package info.bitrich.xchangestream.gateio; - -import info.bitrich.xchangestream.core.StreamingExchangeFactory; -import io.reactivex.rxjava3.disposables.Disposable; -import lombok.extern.slf4j.Slf4j; -import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.currency.CurrencyPair; - -@Slf4j -public class GateioManualExample { - - public static void main(String[] args) throws Exception { - - ExchangeSpecification spec = - StreamingExchangeFactory.INSTANCE - .createExchangeWithoutSpecification(GateioStreamingExchange.class) - .getDefaultExchangeSpecification(); - - GateioStreamingExchange exchange = - (GateioStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); - - exchange.connect().blockingAwait(); - - Disposable sub1 = - exchange - .getStreamingMarketDataService() - .getOrderBook(CurrencyPair.ETH_USDT) - .subscribe( - orderBook -> { - log.info("First ask: {}", orderBook.getAsks().get(0)); - log.info("First bid: {}", orderBook.getBids().get(0)); - }, - throwable -> log.error("ERROR in getting order book: ", throwable)); - Disposable sub2 = - exchange - .getStreamingMarketDataService() - .getOrderBook(CurrencyPair.BTC_USDT) - .subscribe( - orderBook -> { - log.info("First ask: {}", orderBook.getAsks().get(0)); - log.info("First bid: {}", orderBook.getBids().get(0)); - }, - throwable -> log.error("ERROR in getting order book: ", throwable)); - Disposable sub3 = - exchange - .getStreamingMarketDataService() - .getTrades(CurrencyPair.BTC_USDT) - .subscribe( - trade -> { - log.info("Trade Price: {}", trade.getPrice()); - log.info("Trade Amount: {}", trade.getOriginalAmount()); - }, - throwable -> log.error("ERROR in getting trade: ", throwable)); - - Thread.sleep(1000); - sub1.dispose(); - sub2.dispose(); - sub3.dispose(); - } -} diff --git a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingExchangeIT.java b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingExchangeIT.java index 99a20f61f66..e609bd313cb 100644 --- a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingExchangeIT.java +++ b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingExchangeIT.java @@ -1,12 +1,13 @@ package info.bitrich.xchangestream.gateio; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - import info.bitrich.xchangestream.core.StreamingExchangeFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.utils.AuthUtils; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; public class GateioStreamingExchangeIT { @@ -22,6 +23,7 @@ public static void setup() { .getDefaultExchangeSpecification(); spec.setApiKey(System.getProperty("apiKey")); spec.setSecretKey(System.getProperty("secretKey")); + AuthUtils.setApiAndSecretKey(spec, "gateio-main"); exchange = (GateioStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); diff --git a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataServiceTest.java b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataServiceTest.java index 0005835b165..596dd21312d 100644 --- a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataServiceTest.java +++ b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingMarketDataServiceTest.java @@ -1,30 +1,34 @@ package info.bitrich.xchangestream.gateio; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.gateio.config.Config; import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.observers.TestObserver; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.Duration; -import java.time.Instant; -import java.util.Date; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.dto.marketdata.Trade; +import org.knowm.xchange.instrument.Instrument; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) class GateioStreamingMarketDataServiceTest { @@ -120,6 +124,80 @@ void trades() throws Exception { assertThat(actual).usingRecursiveComparison().isEqualTo(expected); } + @Test + void getTradesInstrumentCurrencyPair() throws Exception { + GateioWsNotification notification = readNotification("spot.trades.update.json"); + when(gateioStreamingService.subscribeChannel(eq("spot.trades"), eq(CurrencyPair.BTC_USDT))) + .thenReturn(Observable.just(notification)); + + Observable observable = + gateioStreamingMarketDataService.getTrades((Instrument) CurrencyPair.BTC_USDT); + + TestObserver testObserver = observable.test(); + + Trade actual = testObserver.awaitCount(1).values().get(0); + + testObserver.dispose(); + + assertThat(actual.getInstrument()).isEqualTo(CurrencyPair.BTC_USDT); + } + + @Test + void getTradesUnsupportedInstrument() { + Instrument unsupported = org.mockito.Mockito.mock(Instrument.class); + assertThatThrownBy(() -> gateioStreamingMarketDataService.getTrades(unsupported)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void futuresTrades() throws Exception { + GateioWsNotification notification = readNotification("futures.trades.update.json"); + FuturesContract futuresContract = new FuturesContract(CurrencyPair.BTC_USDT, "PERP"); + when(gateioStreamingService.subscribeChannel(eq("futures.trades"), eq(CurrencyPair.BTC_USDT))) + .thenReturn(Observable.just(notification)); + + Observable observable = + gateioStreamingMarketDataService.getTrades(futuresContract); + + TestObserver testObserver = observable.test(); + + Trade actual = testObserver.awaitCount(1).values().get(0); + + testObserver.dispose(); + + assertThat(actual.getInstrument()).isEqualTo(futuresContract); + } + + @Test + void futuresOrderBook() throws Exception { + GateioWsNotification snapshot = readNotification("futures.obu.snapshot.json"); + GateioWsNotification update = readNotification("futures.obu.update.json"); + FuturesContract futuresContract = new FuturesContract(CurrencyPair.BTC_USDT, "PERP"); + when(gateioStreamingService.subscribeChannel( + eq("futures.obu"), eq(futuresContract), eq(10), eq(Duration.ofMillis(100)))) + .thenReturn(Observable.just(snapshot, update)); + + Observable observable = + gateioStreamingMarketDataService.getOrderBook( + futuresContract, 10, Duration.ofMillis(100)); + + TestObserver testObserver = observable.test(); + + testObserver.awaitCount(2); + OrderBook actual = testObserver.values().get(1); + + testObserver.dispose(); + + assertThat(actual.getTimeStamp()).isEqualTo(Date.from(Instant.ofEpochMilli(1743673027017L))); + assertThat(actual.getBids()).hasSize(5); + assertThat(actual.getAsks()).hasSize(1); + assertThat(actual.getBids().get(0).getLimitPrice()).isEqualByComparingTo("83705.9"); + assertThat(actual.getBids().get(0).getOriginalAmount()).isEqualByComparingTo("30166"); + assertThat(actual.getBids().get(1).getLimitPrice()).isEqualByComparingTo("83702.2"); + assertThat(actual.getBids().get(1).getOriginalAmount()).isEqualByComparingTo("62"); + assertThat(actual.getBids().get(4).getLimitPrice()).isEqualByComparingTo("83685"); + } + private GateioWsNotification readNotification(String resourceName) throws IOException { return objectMapper.readValue( getClass().getClassLoader().getResourceAsStream(resourceName), GateioWsNotification.class); diff --git a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeServiceTest.java b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeServiceTest.java index d0c1cab5628..91234701466 100644 --- a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeServiceTest.java +++ b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/GateioStreamingTradeServiceTest.java @@ -1,29 +1,33 @@ package info.bitrich.xchangestream.gateio; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - import com.fasterxml.jackson.databind.ObjectMapper; import info.bitrich.xchangestream.gateio.config.Config; import info.bitrich.xchangestream.gateio.dto.response.GateioWsNotification; +import info.bitrich.xchangestream.gateio.dto.response.order.GateioSingleOrderNotification; import info.bitrich.xchangestream.gateio.dto.response.usertrade.GateioMultipleUserTradeNotification; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.observers.TestObserver; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.Instant; -import java.util.Date; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.trade.UserTrade; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) class GateioStreamingTradeServiceTest { @@ -73,6 +77,54 @@ void user_trades_btc() throws Exception { assertThat(actual).usingRecursiveComparison().isEqualTo(expected); } + @Test + void order_changes_btc() throws Exception { + GateioWsNotification notification = readNotification("spot.orders.update.json"); + assertThat(notification).isInstanceOf(GateioSingleOrderNotification.class); + + when(gateioStreamingService.subscribeChannel(eq("spot.orders"), eq(CurrencyPair.BTC_USDT))) + .thenReturn(Observable.just(notification)); + + Observable observable = + gateioStreamingTradeService.getOrderChanges(CurrencyPair.BTC_USDT); + + TestObserver testObserver = observable.test(); + + Order actual = testObserver.awaitCount(1).values().get(0); + + testObserver.dispose(); + + assertThat(actual.getId()).isEqualTo("373824296029"); + assertThat(actual.getInstrument()).isEqualTo(CurrencyPair.BTC_USDT); + assertThat(actual.getType()).isEqualTo(OrderType.ASK); + assertThat(actual.getOriginalAmount()).isEqualTo("0.00068"); + } + + @Test + void order_changes_futures_btc() throws Exception { + GateioWsNotification notification = readNotification("spot.orders.update.json"); + assertThat(notification).isInstanceOf(GateioSingleOrderNotification.class); + + FuturesContract futuresContract = new FuturesContract(CurrencyPair.BTC_USDT, "BTC_USDT"); + + when(gateioStreamingService.subscribeChannel(eq("futures.orders"), eq(CurrencyPair.BTC_USDT))) + .thenReturn(Observable.just(notification)); + + Observable observable = + gateioStreamingTradeService.getOrderChanges(futuresContract); + + TestObserver testObserver = observable.test(); + + Order actual = testObserver.awaitCount(1).values().get(0); + + testObserver.dispose(); + + assertThat(actual.getId()).isEqualTo("373824296029"); + assertThat(actual.getInstrument()).isEqualTo(futuresContract.getCurrencyPair()); + assertThat(actual.getType()).isEqualTo(OrderType.ASK); + assertThat(actual.getOriginalAmount()).isEqualTo("0.00068"); + } + private GateioWsNotification readNotification(String resourceName) throws IOException { return objectMapper.readValue( getClass().getClassLoader().getResourceAsStream(resourceName), GateioWsNotification.class); diff --git a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java new file mode 100644 index 00000000000..4081da345b9 --- /dev/null +++ b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioFuturesManualExample.java @@ -0,0 +1,78 @@ +package info.bitrich.xchangestream.gateio.examples; + +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import info.bitrich.xchangestream.gateio.GateioStreamingExchange; +import io.reactivex.rxjava3.disposables.Disposable; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.utils.AuthUtils; + +import static org.knowm.xchange.gateio.GateioExchange.EXCHANGE_TYPE; +import static org.knowm.xchange.gateio.dto.GateioExchangeType.FUTURES; + +@Slf4j +public class GateioFuturesManualExample { + private final Instrument instrument = new FuturesContract("ETH/USDT/PERP"); + public GateioStreamingExchange exchange; + private final boolean logOutput = true; + + @Before + public void before() { + init(); + } + + @Test + @Ignore + public void geOrderBook() throws InterruptedException { + Disposable disposable = exchange.getStreamingMarketDataService().getOrderBook(instrument, 400).subscribe( + orderBook -> { + if (logOutput) { + log.info("{}", orderBook); + } + }); + Thread.sleep(1000); + disposable.dispose(); + } + + @Test + @Ignore + public void getUserOrders() throws InterruptedException { + Disposable disposable = exchange.getStreamingMarketDataService().getTrades(instrument).subscribe( + trade -> { + if (logOutput) { + log.info("{}", trade); + } + }); + Thread.sleep(3000); + disposable.dispose(); + } + + @Test + @Ignore + public void getTrades() throws InterruptedException { + Disposable disposable = exchange.getStreamingMarketDataService().getTrades(instrument).subscribe( + trade -> { + if (logOutput) { + log.info("{}", trade); + } + }); + Thread.sleep(3000); + disposable.dispose(); + } + + private void init() { + ExchangeSpecification spec = + StreamingExchangeFactory.INSTANCE + .createExchangeWithoutSpecification(GateioStreamingExchange.class) + .getDefaultExchangeSpecification(); + spec.setExchangeSpecificParametersItem(EXCHANGE_TYPE, FUTURES); + AuthUtils.setApiAndSecretKey(spec, "gateio-main"); + exchange = (GateioStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); + exchange.connect().blockingAwait(); + } +} diff --git a/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioSpotManualExample.java b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioSpotManualExample.java new file mode 100644 index 00000000000..88c09502abc --- /dev/null +++ b/xchange-stream-gateio/src/test/java/info/bitrich/xchangestream/gateio/examples/GateioSpotManualExample.java @@ -0,0 +1,61 @@ +package info.bitrich.xchangestream.gateio.examples; + +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import info.bitrich.xchangestream.gateio.GateioStreamingExchange; +import io.reactivex.rxjava3.disposables.Disposable; +import lombok.extern.slf4j.Slf4j; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.currency.CurrencyPair; + +@Slf4j +public class GateioSpotManualExample { + + public static void main(String[] args) throws Exception { + + ExchangeSpecification spec = + StreamingExchangeFactory.INSTANCE + .createExchangeWithoutSpecification(GateioStreamingExchange.class) + .getDefaultExchangeSpecification(); + + GateioStreamingExchange exchange = + (GateioStreamingExchange) StreamingExchangeFactory.INSTANCE.createExchange(spec); + + exchange.connect().blockingAwait(); + +// Disposable sub1 = +// exchange +// .getStreamingMarketDataService() +// .getOrderBook(CurrencyPair.ETH_USDT) +// .subscribe( +// orderBook -> { +// log.info("First ask: {}", orderBook.getAsks().get(0)); +// log.info("First bid: {}", orderBook.getBids().get(0)); +// }, +// throwable -> log.error("ERROR in getting order book: ", throwable)); +// Disposable sub2 = +// exchange +// .getStreamingMarketDataService() +// .getOrderBook(CurrencyPair.BTC_USDT) +// .subscribe( +// orderBook -> { +// log.info("First ask: {}", orderBook.getAsks().get(0)); +// log.info("First bid: {}", orderBook.getBids().get(0)); +// }, +// throwable -> log.error("ERROR in getting order book: ", throwable)); + Disposable sub3 = + exchange + .getStreamingMarketDataService() + .getTrades(CurrencyPair.BTC_USDT) + .subscribe( + trade -> { + log.info("Trade Price: {}", trade.getPrice()); + log.info("Trade Amount: {}", trade.getOriginalAmount()); + }, + throwable -> log.error("ERROR in getting trade: ", throwable)); + + Thread.sleep(10000); +// sub1.dispose(); +// sub2.dispose(); + sub3.dispose(); + } +} diff --git a/xchange-stream-gateio/src/test/resources/futures.obu.snapshot.json b/xchange-stream-gateio/src/test/resources/futures.obu.snapshot.json new file mode 100644 index 00000000000..984beb35f3d --- /dev/null +++ b/xchange-stream-gateio/src/test/resources/futures.obu.snapshot.json @@ -0,0 +1,22 @@ +{ + "channel": "futures.obu", + "result": { + "t": 1743673026995, + "full": true, + "s": "ob.BTC_USDT.400", + "u": 79072179673, + "b": [ + [ + "83705.9", + "30166" + ] + ], + "a": [ + [ + "83706", + "4208" + ] + ] + }, + "time_ms": 1743673026999 +} \ No newline at end of file diff --git a/xchange-stream-gateio/src/test/resources/futures.obu.update.json b/xchange-stream-gateio/src/test/resources/futures.obu.update.json new file mode 100644 index 00000000000..ae821147274 --- /dev/null +++ b/xchange-stream-gateio/src/test/resources/futures.obu.update.json @@ -0,0 +1,32 @@ +{ + "channel": "futures.obu", + "result": { + "t": 1743673027017, + "s": "ob.BTC_USDT.400", + "U": 79072179674, + "u": 79072179694, + "b": [ + [ + "83702.2", + "62" + ], + [ + "83702.1", + "0" + ], + [ + "83702", + "0" + ], + [ + "83685.6", + "120" + ], + [ + "83685", + "239" + ] + ] + }, + "time_ms": 1743673027020 +} \ No newline at end of file diff --git a/xchange-stream-gateio/src/test/resources/futures.trades.update.json b/xchange-stream-gateio/src/test/resources/futures.trades.update.json new file mode 100644 index 00000000000..cba71936546 --- /dev/null +++ b/xchange-stream-gateio/src/test/resources/futures.trades.update.json @@ -0,0 +1,17 @@ +{ + "channel": "futures.trades", + "event": "update", + "time": 1541503698, + "time_ms": 1541503698123, + "result": [ + { + "size": "-108", + "id": 27753479, + "create_time": 1545136464, + "create_time_ms": 1545136464123, + "price": "96.4", + "contract": "BTC_USDT", + "is_internal": true + } + ] +} diff --git a/xchange-stream-gateio/src/test/resources/logback.xml b/xchange-stream-gateio/src/test/resources/logback.xml index 5809b573500..1f3682831b3 100644 --- a/xchange-stream-gateio/src/test/resources/logback.xml +++ b/xchange-stream-gateio/src/test/resources/logback.xml @@ -17,7 +17,8 @@ - - + + + diff --git a/xchange-stream-gateio/src/test/resources/spot.orders.update.json b/xchange-stream-gateio/src/test/resources/spot.orders.update.json new file mode 100644 index 00000000000..1a899c39e7d --- /dev/null +++ b/xchange-stream-gateio/src/test/resources/spot.orders.update.json @@ -0,0 +1,33 @@ +{ + "time": 1690498746, + "time_ms": 1690498746773, + "channel": "spot.orders", + "event": "update", + "result": { + "id": "373824296029", + "text": "t-valid-limit-sell-order", + "create_time_ms": 1690498746773, + "update_time_ms": 1690498746773, + "status": "open", + "currency_pair": "BTC_USDT", + "type": "limit", + "account": "spot", + "side": "sell", + "amount": "0.00068", + "price": "29240.7", + "time_in_force": "gtc", + "iceberg": "0", + "left": "0.00068", + "filled_total": "0", + "fee": "0", + "fee_currency": "USDT", + "point_fee": "0", + "gt_fee": "0", + "gt_maker_fee": "0", + "gt_taker_fee": "0", + "gt_discount": false, + "rebated_fee": "0", + "rebated_fee_currency": "BTC", + "finish_as": "open" + } +}