diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1851d4b..4da65c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,14 @@ env: jobs: test: - name: Build and Test + name: Test (Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}}) runs-on: ubuntu-latest + strategy: + matrix: + elixir: ["1.18.4"] + otp: ["28.0"] + services: postgres: image: timescale/timescaledb:latest-pg16 @@ -31,6 +36,16 @@ jobs: ports: - 5432:5432 + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + steps: - name: Checkout code uses: actions/checkout@v4 @@ -38,8 +53,8 @@ jobs: - name: Set up Elixir uses: erlef/setup-beam@v1 with: - elixir-version: ${{ env.ELIXIR_VERSION }} - otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} - name: Restore dependencies cache uses: actions/cache@v4 @@ -47,30 +62,130 @@ jobs: path: | deps _build - key: ${{ runner.os }}-mix-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-v2-${{ hashFiles('**/mix.lock') }} + key: ${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} restore-keys: | - ${{ runner.os }}-mix-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-v2- - - - name: Clean dependencies - run: mix deps.clean --all || true + ${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}- - name: Install dependencies run: mix deps.get + - name: Compile dependencies + run: mix deps.compile + - name: Compile application - run: mix compile + run: mix compile --warnings-as-errors - name: Check formatting run: mix format --check-formatted - - name: Create and migrate database + - name: Run Credo + run: mix credo --strict + + - name: Create test database env: DATABASE_URL: postgres://postgres:postgres@localhost:5432/binance_trading_test - run: | - mix ecto.create - mix ecto.migrate + run: mix ecto.create + + - name: Run migrations + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/binance_trading_test + run: mix ecto.migrate - name: Run tests env: DATABASE_URL: postgres://postgres:postgres@localhost:5432/binance_trading_test - run: mix test + REDIS_URL: redis://localhost:6379/0 + run: mix test --cover --warnings-as-errors + + - name: Generate coverage report + run: mix coveralls.html + continue-on-error: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: ./cover/excoveralls.json + fail_ci_if_error: false + continue-on-error: true + + dialyzer: + name: Dialyzer + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.ELIXIR_VERSION }} + otp-version: ${{ env.OTP_VERSION }} + + - name: Restore dependencies cache + uses: actions/cache@v4 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}- + + - name: Restore PLT cache + uses: actions/cache@v4 + with: + path: priv/plts + key: ${{ runner.os }}-plt-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-plt-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}- + + - name: Install dependencies + run: mix deps.get + + - name: Run Dialyzer + run: mix dialyzer --format github + continue-on-error: true + + security: + name: Security Audit + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.ELIXIR_VERSION }} + otp-version: ${{ env.OTP_VERSION }} + + - name: Install dependencies + run: mix deps.get + + - name: Check for retired dependencies + run: mix hex.audit + continue-on-error: true + + build-docker: + name: Build Docker Image + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build development image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.dev + push: false + tags: binance-trading-dev:latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/FORMAT_BEFORE_MERGE.md b/FORMAT_BEFORE_MERGE.md new file mode 100644 index 0000000..9540d68 --- /dev/null +++ b/FORMAT_BEFORE_MERGE.md @@ -0,0 +1,38 @@ +# Форматирование кода перед merge + +## Быстрый способ - одна команда: + +```bash +mix format +git add . +git commit -m "chore: auto-format code with mix format" +git push +``` + +## Если у вас Docker: + +```bash +docker-compose run app mix format +git add . +git commit -m "chore: auto-format code with mix format" +git push +``` + +## Или через make (если есть в Makefile): + +```bash +make format +git add . +git commit -m "chore: auto-format code with mix format" +git push +``` + +## После форматирования: + +Все файлы будут автоматически приведены к стандарту Elixir formatter. +Затем можно будет делать merge в master без проблем с CI. + +## Файлы, которые нужно отформатировать: + +Список файлов был предоставлен в сообщении об ошибке CI. +`mix format` автоматически обработает их все. diff --git a/apps/dashboard_web/lib/dashboard_web/components/core_components.ex b/apps/dashboard_web/lib/dashboard_web/components/core_components.ex index d065a9c..94239e6 100644 --- a/apps/dashboard_web/lib/dashboard_web/components/core_components.ex +++ b/apps/dashboard_web/lib/dashboard_web/components/core_components.ex @@ -126,7 +126,7 @@ defmodule DashboardWeb.CoreComponents do ) end - defp hide(js \\ %JS{}, selector) do + defp hide(js, selector) do JS.hide(js, to: selector, time: 200, diff --git a/apps/dashboard_web/lib/dashboard_web/gettext.ex b/apps/dashboard_web/lib/dashboard_web/gettext.ex index 7f5f774..85114fc 100644 --- a/apps/dashboard_web/lib/dashboard_web/gettext.ex +++ b/apps/dashboard_web/lib/dashboard_web/gettext.ex @@ -1,3 +1,3 @@ defmodule DashboardWeb.Gettext do - use Gettext, otp_app: :dashboard_web + use Gettext.Backend, otp_app: :dashboard_web end diff --git a/apps/dashboard_web/lib/dashboard_web/live/history_live.ex b/apps/dashboard_web/lib/dashboard_web/live/history_live.ex index 8dffb64..accda7d 100644 --- a/apps/dashboard_web/lib/dashboard_web/live/history_live.ex +++ b/apps/dashboard_web/lib/dashboard_web/live/history_live.ex @@ -1,7 +1,6 @@ defmodule DashboardWeb.HistoryLive do use DashboardWeb, :live_view - - alias SharedData.{Trading} + alias SharedData.Helpers.DecimalHelper @impl true diff --git a/apps/dashboard_web/lib/dashboard_web/live/portfolio_live.ex b/apps/dashboard_web/lib/dashboard_web/live/portfolio_live.ex index 432ffec..6ff8513 100644 --- a/apps/dashboard_web/lib/dashboard_web/live/portfolio_live.ex +++ b/apps/dashboard_web/lib/dashboard_web/live/portfolio_live.ex @@ -1,7 +1,6 @@ defmodule DashboardWeb.PortfolioLive do use DashboardWeb, :live_view - - alias SharedData.{Accounts, Trading} + alias SharedData.Helpers.DecimalHelper @impl true @@ -154,7 +153,7 @@ defmodule DashboardWeb.PortfolioLive do |> assign(total_pnl: Decimal.new(0)) end - defp calculate_value(balance) do + defp calculate_value(_balance) do # TODO: Calculate USDT value based on current prices # For now, return dash "-" diff --git a/apps/dashboard_web/lib/dashboard_web/live/settings_live.ex b/apps/dashboard_web/lib/dashboard_web/live/settings_live.ex index 88265e9..d0236b7 100644 --- a/apps/dashboard_web/lib/dashboard_web/live/settings_live.ex +++ b/apps/dashboard_web/lib/dashboard_web/live/settings_live.ex @@ -1,7 +1,5 @@ defmodule DashboardWeb.SettingsLive do use DashboardWeb, :live_view - - alias SharedData.{Accounts, Trading} @impl true def mount(_params, _session, socket) do @@ -22,7 +20,7 @@ defmodule DashboardWeb.SettingsLive do end @impl true - def handle_event("activate_strategy", %{"id" => strategy_id}, socket) do + def handle_event("activate_strategy", %{"id" => _strategy_id}, socket) do # TODO: Implement strategy activation {:noreply, put_flash(socket, :info, "Strategy activation requested")} end diff --git a/apps/dashboard_web/lib/dashboard_web/live/trading_live.ex b/apps/dashboard_web/lib/dashboard_web/live/trading_live.ex index 16ef4d4..e3943c5 100644 --- a/apps/dashboard_web/lib/dashboard_web/live/trading_live.ex +++ b/apps/dashboard_web/lib/dashboard_web/live/trading_live.ex @@ -1,7 +1,6 @@ defmodule DashboardWeb.TradingLive do use DashboardWeb, :live_view - - alias SharedData.{Accounts, Trading} + alias SharedData.Helpers.DecimalHelper @impl true @@ -28,7 +27,7 @@ defmodule DashboardWeb.TradingLive do end @impl true - def handle_info({:execution_report, data}, socket) do + def handle_info({:execution_report, _data}, socket) do # Reload orders when execution report received {:noreply, load_data(socket)} end @@ -42,7 +41,7 @@ defmodule DashboardWeb.TradingLive do def handle_info(_, socket), do: {:noreply, socket} @impl true - def handle_event("cancel_order", %{"id" => order_id}, socket) do + def handle_event("cancel_order", %{"id" => _order_id}, socket) do # TODO: Implement order cancellation {:noreply, put_flash(socket, :info, "Order cancellation requested")} end diff --git a/apps/dashboard_web/mix.exs b/apps/dashboard_web/mix.exs index f327ca1..8711feb 100644 --- a/apps/dashboard_web/mix.exs +++ b/apps/dashboard_web/mix.exs @@ -36,6 +36,7 @@ defmodule DashboardWeb.MixProject do {:phoenix_live_dashboard, "~> 0.8"}, {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.20"}, {:jason, "~> 1.4"}, {:plug_cowboy, "~> 2.6"}, {:shared_data, in_umbrella: true}, diff --git a/apps/data_collector/mix.exs b/apps/data_collector/mix.exs index 23862f1..cb90c85 100644 --- a/apps/data_collector/mix.exs +++ b/apps/data_collector/mix.exs @@ -28,7 +28,7 @@ defmodule DataCollector.MixProject do defp deps do [ - {:binance, "~> 1.0"}, + {:binance, "~> 2.0"}, {:websockex, "~> 0.4"}, # httpoison is already included as a dependency of binance (~> 1.4) {:jason, "~> 1.4"}, diff --git a/apps/shared_data/.formatter.exs b/apps/shared_data/.formatter.exs index 6c7b69a..b9edaa8 100644 --- a/apps/shared_data/.formatter.exs +++ b/apps/shared_data/.formatter.exs @@ -1 +1,4 @@ -.gitignore +[ + import_deps: [:ecto, :ecto_sql], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/apps/shared_data/lib/shared_data/pubsub.ex b/apps/shared_data/lib/shared_data/pubsub.ex index c5aac97..b1794ed 100644 --- a/apps/shared_data/lib/shared_data/pubsub.ex +++ b/apps/shared_data/lib/shared_data/pubsub.ex @@ -8,7 +8,7 @@ defmodule SharedData.PubSub do ## Topics ### Market Data - - `market:#{symbol}` - Ticker and trade updates for a specific symbol + - `market:\#{symbol}` - Ticker and trade updates for a specific symbol - Messages: `{:ticker, data}`, `{:trade, data}` - Publishers: BinanceWebSocket - Subscribers: MarketData, Trader, TradingLive diff --git a/apps/trading_engine/lib/trading_engine/risk_manager.ex b/apps/trading_engine/lib/trading_engine/risk_manager.ex index 0bfb6de..cfbbf3c 100644 --- a/apps/trading_engine/lib/trading_engine/risk_manager.ex +++ b/apps/trading_engine/lib/trading_engine/risk_manager.ex @@ -49,9 +49,10 @@ defmodule TradingEngine.RiskManager do defp check_position_size(_, _), do: :ok @spec check_daily_loss(map()) :: :ok | {:error, String.t()} - defp check_daily_loss(state) do + defp check_daily_loss(_state) do # This would need to query database for today's trades # For now, simplified implementation + # TODO: Implement actual daily loss check using @max_daily_loss :ok end diff --git a/apps/trading_engine/lib/trading_engine/strategies/naive.ex b/apps/trading_engine/lib/trading_engine/strategies/naive.ex index fba4867..9145dda 100644 --- a/apps/trading_engine/lib/trading_engine/strategies/naive.ex +++ b/apps/trading_engine/lib/trading_engine/strategies/naive.ex @@ -90,20 +90,20 @@ defmodule TradingEngine.Strategies.Naive do Decimal.sub(current_price, state.last_price), state.last_price ) - - Decimal.compare(price_change, Decimal.minus(state.buy_down_interval)) == :lt + + Decimal.compare(price_change, Decimal.negate(state.buy_down_interval)) == :lt end defp should_sell?(_current_price, %{position: nil}), do: false defp should_sell?(current_price, state) do entry_price = state.position.entry_price - + price_change = Decimal.div( Decimal.sub(current_price, entry_price), entry_price ) - + Decimal.compare(price_change, state.sell_up_interval) == :gt end end diff --git a/apps/trading_engine/lib/trading_engine/trader.ex b/apps/trading_engine/lib/trading_engine/trader.ex index 461d8d3..133b4a4 100644 --- a/apps/trading_engine/lib/trading_engine/trader.ex +++ b/apps/trading_engine/lib/trading_engine/trader.ex @@ -9,8 +9,6 @@ defmodule TradingEngine.Trader do require Logger alias DataCollector.BinanceClient - alias TradingEngine.OrderManager - alias TradingEngine.PositionTracker alias TradingEngine.RiskManager alias SharedData.{Config, Types}