Trader en PowerShell sur Binance !

Hello les techies !

Dernier article de la série sur l’utilisation de l’API Binance en PowerShell

Un petit récap de ce que l’on a fait :

Partie 1 : Récupérer le prix d’une crypto sur Binance en PowerShell

Partie 2 : Récupérer le solde d’un compte Binance en PowerShell

Et pour cette partie 3, c’est dans le titre ! on va trader 🙂 enfin on va passer des ordres d’achats et on va pouvoir choisir entre les 3 types d’ordres, Market, Limit et Stop Limit.

Comme Déjà dit dans les articles précédents, le code original n’est pas de moi, j’ai cependant fait quelques ajustements car celui-ci ne fonctionnait pas très bien.

En plus de ça, après ces 3 articles sur l’API de Binance en PowerShell, je m’attaque à la compilation de tout ça dans un module (avec quelques fonctionnalités en plus comme la prise en charge des ordres « test » pour ne pas faire de boulettes ^^)

Les fonctions

Si vous avez lus les articles précédents, vous allez voir que l’on reprend les mêmes fonctions de base comme l’authentification.

Ce qui va changer pour Trader en PowerShell c’est la fonction pour passer les ordres

On commence donc par créer un fichier New-Order.ps1 et on colle nos fonctions de base (je vous invite à consulter les articles précédent si vous souhaitez plus d’explications sur leurs rôles)

Il vous faudra bien entendu renseigner votre Clé API et votre Secret (pour récupérer ça sur votre compte Binance, j’en parle dans la Partie 2)

function Get-UnixTimeStamp {
    <#
		.SYNOPSIS
		Return the timestamp in millisecond of the Unix Epoch

		.DESCRIPTION
		Unix Epoch started the Thursday, January 1, 1970 12:00:00 AM. The function return the number of second from that time.

		.EXAMPLE
		Get-UnixTimeStamp
		
	#>
    param(

    )

    $URL = "https://api.binance.com/api/v3/time"

    $TimeStamp = Invoke-RestMethod -Uri $URL -Method get 

    return $TimeStamp.serverTime
}

function Get-BinanceAPISignature {
    <#
		.SYNOPSIS
		Prepare the signature that will be sent with the API request

		.DESCRIPTION
		Endpoint requires sending a valid API-Key and signature

		.PARAMETER QueryString
        The queryString must contains the symobol, timestamp and a recvWindow

        .PARAMETER EndPoint
        The EndPoint you want to request

		.EXAMPLE
		$URI = Get-BinanceAPISignature -QueryString $QueryString -EndPoint "/api/v3/openOrders"
		
	#>
    param(
        [Parameter(Mandatory = $true)]$QueryString,
        [Parameter(Mandatory = $true)]$EndPoint
    )
  
    $APISecret = "Votre API Secret"

    $hmacsha = New-Object System.Security.Cryptography.HMACSHA256
    $hmacsha.key = [Text.Encoding]::ASCII.GetBytes($APISecret)
    $signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($QueryString))
    $signature = [System.BitConverter]::ToString($signature).Replace('-', '').ToLower()

    $URI = "https://api.binance.com$($EndPoint)?$QueryString&signature=$signature"
	
    return $URI
}

function Get-BinanceAPIHeader {
    <#
		.SYNOPSIS
		Prepare the header that will be sent with the API request

		.DESCRIPTION
		The header include your APIKey

		.PARAMETER 
        #APIKey

		.EXAMPLE
		Get-BinanceAPIHeader
		
	#>
    param(
        
    )
  
    $APIKey = "Votre Clé API"

    $Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $Headers.Add("X-MBX-APIKEY", $APIKey)

    return $Headers
}

function Request-API {
    <#
		.SYNOPSIS
		Run the CURL command with defined parameters

		.DESCRIPTION
		Call the API and error handling. Return the result of the request

		.PARAMETER Method
        Choose a method according to the EndPoint

		.PARAMETER URI
        This parameter needs to be obtained with Get-BinanceAPISignature

		.PARAMETER Headers
        This parameter needs to be obtained with Get-BinanceAPIHeaderx

		.EXAMPLE
		$ObjResults = Request-API -Method Get -URI $URI -Headers $Headers
	#>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true)][ValidateSet("POST", "GET", "DELETE")]$Method,
        [Parameter(Mandatory = $true)]$URI,
        [Parameter(Mandatory = $true)]$Headers
    )

    try {
        $ArrayJsonResult = Invoke-RestMethod $URI -Method $Method -Headers $Headers #-Verbose
    }
    catch {
        $LastError = $Error[0].ErrorDetails.Message | ConvertFrom-Json

        <#
        write-host "1: " $ErrResp -b Red
        write-host "2: " $LastError.code -b Red
        write-host "3: " $LastError.msg -b Red
        #>

        switch ($LastError.code) {
            ("-1021") { write-host "TimeStamp Is outside of the recvWindow" }
            ("-1105") { write-host "TimeStamp issue" }
            ("-1003") { write-host "Too much request, IP Banned"; break }
            ("-2010") { write-host "Stop price would trigger immediately or Account has too many open stop loss and/or take profit orders on the symbol." }
            ("-1013") { write-host "The amount is not enough for this currency or not following the step size rule for the symbol." } 
            ("-1111") { write-host "Too many decimal check the precision required with Get-ExchangeInfo" }  
        }
    }
	
    return $ArrayJsonResult
}

Ensuite, et bien on va reprendre la fonction de la Partie 1, car pour les ordres Limit et Stop Limit il nous faut le prix actuel du token que l’on veut acheter ou vendre. plus précisément vous allez voir plus bas, c’est si on veut passer l’ordre en FIAT. c’est à dire en Euros ou en USDT par exemple.

C’est pour déterminer la quantité de token que l’on peut acheter avec nos FIAT.

function Get-Price {
    
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $false)] 
        [string]$Symbol,

        [Parameter(Mandatory = $false)]
        [ValidateSet(0, 1, 2, 3, 4, 5, 6, 7, 8)]
        [string]$Decimal = 3
    )

    BEGIN {
        if ($PSBoundParameters.ContainsKey('Symbol')) {
            $URL = "https://api.binance.com/api/v3/ticker/price?symbol=" + $Symbol
        }
        else {
            $URL = "https://api.binance.com/api/v3/ticker/price"
        }
    }
    
    PROCESS {
        $Ticker = Invoke-RestMethod -Uri $URL -Method get
    }

    END {
        if ($PSBoundParameters.ContainsKey('Symbol')) {
            return [math]::Round($Ticker.Price, $Decimal)
        }
        else {
            return $Ticker
        }
    }

}

Et enfin nous avons la fonction principale pour créer nos requêtes d’ordres Market, Limit ou Stop Limit

Explication et tests plus bas

function New-Order {
    <#
		.SYNOPSIS
		Place an order to buy or sell crypto

        .PARAMETER Symbol
        The crypto you want to buy or sell

        .PARAMETER Side
        ValidateSet "BUY" or "SELL"

        .PARAMETER OrderType
        ValidateSet "OrderMarket" or "OrderLimit" or "OrderStopLimit"

        .PARAMETER Quantity
        Specifies the amount you want to spend (when buying) or receive (when selling)

        .PARAMETER FiatAmount
        specifies the amount you want to spend in USDT 

        .EXAMPLE
        New-Order -Symbol BTCUSDT -Side BUY -OrderType OrderMarket -FiatAmount 20 -Verbose
        New-Order -Symbol ETHEUR -Side BUY -OrderType OrderMarket -FiatAmount 10 -Verbose

        .EXAMPLE
        New-Order -Symbol BTCUSDT -Side BUY -OrderType OrderLimit -FiatAmount 1000 -Price 33000    
        
        .EXAMPLE
        New-Order -Symbol BTCUSDT -Side BUY -OrderType OrderStopLimit -Quantity 0.002 -Price 33000 -StopPrice 36000

        .EXAMPLE
        New-Order -Symbol XRPUSDT -Side SELL -OrderType OrderLimit -Quantity 100 -Price 0.5 
        New-Order -Symbol EGLDUSDT -Side SELL -OrderType OrderMarket -Quantity 0.07 -Verbose
        
        .EXAMPLE
        New-Order -Symbol XRPUSDT -Side SELL -OrderType OrderStopLimit -Quantity 100 -Price 0.55 -StopPrice 0.5

        .NOTES
        Common error : The amount is not enough for this currency or not following the step size rule for the symbol.
        If you have set an small Ammount, Example: min quantity for EUR is 10
	#>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true)] 
        [string]$Symbol,

        [Parameter(Mandatory = $true)] 
        [ValidateSet("BUY", "SELL")]
        [string]$Side,

        [Parameter(Mandatory = $true)] 
        [ValidateSet("OrderMarket", "OrderLimit", "OrderStopLimit")]
        [string]$OrderType,

        [Parameter(Mandatory = $true, ParameterSetName = 'quantity')]
        [double]$Quantity,
    
        [Parameter(Mandatory = $true, ParameterSetName = 'quantity2')] 
        [double]$FiatAmount
    )

    DynamicParam {
        $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary

        if ($OrderType -ne "OrderMarket") {
            # price param
            $attributes = New-Object -Type System.Management.Automation.ParameterAttribute
            $attributes.Mandatory = $true
            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)

            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Price", [double], $attributeCollection)
            $paramDictionary.Add("Price", $dynParam1)
        }
      
        if ($OrderType -eq "OrderStopLimit") {
            # StopPrice param
            $attributes2 = New-Object -Type System.Management.Automation.ParameterAttribute
            $attributes2.Mandatory = $true
            $attributeCollection2 = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection2.Add($attributes2)

            $dynParam2 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("StopPrice", [double], $attributeCollection2)
            $paramDictionary.Add("StopPrice", $dynParam2)
        }

        return $paramDictionary
    }

    BEGIN {
        # Check prerequisit
        try {
            Get-Command -Name Get-UnixTimeStamp -ErrorAction Stop | out-null
            Get-Command -name Get-BinanceAPISignature -ErrorAction Stop | Out-Null
            Get-Command -Name Get-BinanceAPIHeader -ErrorAction Stop | Out-Null
            Get-Command -Name Request-API -ErrorAction Stop | Out-Null
        }
        catch {
            Write-Host "Load Get-UnixTimeStamp, Get-BinanceAPISignature, Get-BinanceAPIHeader, Request-API  first prior to laod the current script" -b red
            Break
        }

        $TimeStamp = Get-UnixTimeStamp
        # retrieve value from dyn param
        if ($OrderType -ne "OrderMarket") {
            $Price = $paramDictionary.values[0].value[0]
            $StopPrice = $paramDictionary.values[0].value[1]
        }

        switch ($OrderType) {
            "OrderMarket" {
                if ($PSBoundParameters.ContainsKey('FiatAmount')) {
                    Write-Verbose "Order Market with FIAT"                                                   
                    $QueryString = "symbol=$Symbol&side=$Side&type=MARKET&quoteOrderQty=$FiatAmount&timestamp=$TimeStamp&recvWindow=5000"
                }
                else {          
                    Write-Verbose "Order Market with Quantity"
                    $QueryString = "symbol=$Symbol&side=$Side&type=MARKET&quantity=$Quantity&timestamp=$TimeStamp&recvWindow=5000"
                }
            }
            
            "OrderLimit" {
                if ($PSBoundParameters.ContainsKey('FiatAmount')) {
                    $CurrentPrice = Get-Price -Symbol $Symbol -Decimal 8
                    $Quantity = [math]::Round($FiatAmount / $CurrentPrice, 6)
                }
                $QueryString = "symbol=$Symbol&side=$Side&type=LIMIT&price=$Price&timeInForce=GTC&quantity=$Quantity&timestamp=$TimeStamp&recvWindow=5000"
            }

            "OrderStopLimit" {
                if ($PSBoundParameters.ContainsKey('FiatAmount')) {
                    $CurrentPrice = Get-Price -Symbol $Symbol -Decimal 0
                    $Quantity = [math]::Round($FiatAmount / $CurrentPrice, 6)
                }
                $QueryString = "symbol=$Symbol&side=$Side&type=TAKE_PROFIT_LIMIT&stopPrice=$StopPrice&price=$Price&timeInForce=GTC&quantity=$Quantity&timestamp=$TimeStamp&recvWindow=5000"
            }
        }
    }
    
    PROCESS {
        $URI = Get-BinanceAPISignature -QueryString $QueryString -EndPoint "/api/v3/order"
        $Headers = Get-BinanceAPIHeader
        $ObjResults = $null # need to do this?
        $ObjResults = Request-API -Method POST -URI $URI -Headers $Headers
    }

    END {
        return $ObjResults
    }
}

Les tests

Je vous explique le fonctionnement avec des exemples concret.

Premier cas, je souhaite acheter de l’Ether avec des Euros, je regarde donc, comme pour get-price le symbol et dans mon cas c’est ETHEUR

Ensuite je souhaite acheter donc la side sera BUY et pour le OrderType on va faire un ordre direct pour commencer donc OrderMarket

Sur le dernier Paramètre on a le choix entre Quantity et FiatAmount. Soit je définie une quantité d’Ether, 1 par exemple et donc il me faudra 2577 euros disponibles dans le portefeuille sinon: erreur ^^.

Ou alors je définie une quantité d’Euros, comme 50 euros par exemple et là j’obtiendrais 0,01940059 Ether

Tout ça dépend évidement du prix du marché à l’instant où vous passez l’ordre !

Pour 10 euros la commande sera :

New-Order -Symbol ETHEUR -Side BUY -OrderType OrderMarket -FiatAmount 10 -Verbose

Je ne vais pas tester tout les cas dans cet article, ce que je peux vous dire c’est que ça fonctionne 😉

Juste un dernier, pour vendre des EGOLD vers de l’USDT en utilisant la quantité, la commande sera :

New-Order -Symbol EGLDUSDT -Side SELL -OrderType OrderMarket -Quantity 0.07 -Verbose

Gestion des erreurs

La gestion des erreurs se fait via la fontion Request-API, elle gère certains cas.

Par contre d’autres erreurs peuvent être retounées si la quantité minimum n’est pas bonne par exemple.

Dans mes tests j’ai eu cette erreur :

The amount is not enough for this currency or not following the step size rule for the symbol.

En fait c’est parce que le montant de l’ordre n’est pas assez important.

Pour un achat en EUR le minimum est de 10 euros, donc si on essaye de passer un ordre avec -FiatAmount 5, cela ne fonctionne pas et pareil pour vendre.

Conclusion

Dans cette série sur l’API de Binance en PowerShell nous avons vu comment récupérer le prix d’un actif, comment récupérer son portefeuille spot et comment passer des ordres spot, tout ça directement depuis le terminal !

Pour ma part je publie bientôt un module qui regroupe ces fonctionnalités plus quelques autres et j’en profite aussi pour gérer la config (Clé API, Secret…)

Cela permet ensuite de créer un bot par exemple, attention quand même car il y des des limites en nombre de requêtes et d’ordres. la doc ici : https://binance-docs.github.io/apidocs/futures/en/#limits

A+ les techies !

Zalman VE500

Voila un outil indispensable que tout technicien doit avoir avec lui, c’est ce boitier qui contient un HDD/SSD et avec le quel on va pouvoir tout simplement booter sur l’image disque que l’on souhaite (ISO, IMG). d’autres options sont aussi présentes comme la sélection du mode HDD, VCD ou les deux ainsi que le cryptage du disque et le verrouillage en écriture.

On en parle sur le blog !

Vous aimerez aussi...

Laisser un commentaire

%d blogueurs aiment cette page :