Hello Betfair: The Same Strategy in F#, C#, VB.NET, and an AI Prompt (bfexplorer)

Hello Betfair: The Same Strategy in F#, C#, VB.NET, and an AI Prompt (bfexplorer)

Source: Dev.to

The strategy logic ## Where the examples live ## 1) F# — shortest code, runs from source ## 2) C# — same logic, packaged as a DLL ## 3) Visual Basic — same DLL workflow, different syntax ## 4) AI prompt — describe the intent, let bfexplorer/MCP execute ## Practical notes (things that bite people) ## Wrap-up bfexplorer lets you automate trading strategies in a few different “shapes”: This post uses the same “Hello Betfair” strategy idea in all approaches with one main goal: help you see the code differences (syntax, interop, and packaging) while keeping the trading logic constant. From the repo’s HelloBetfair overview, the core rule is: In the code examples below the default range is 2.5 to 3.0 (inclusive). The trigger looks for a suitable selection (favourites in these examples) and either: bfexplorer is built with F#, and it supports F# scripting. That means you can execute a BotTrigger directly from the .fsx file. If you prefer a compiled workflow, you can also package an F# trigger as a DLL assembly (similar to the C#/VB.NET examples). The nice part is that you get to choose: script for iteration speed, DLL for deployment/versioning. Here’s the HelloBetfair F# example: What I like about this one: In C#, you implement the same IBotTrigger interface, but you compile the project to a DLL. HelloBetfair C# trigger: Two very “real-world” details show up here: VB.NET is functionally the same as the C# approach: you build a DLL and load it. HelloBetfair VB.NET trigger: The AI example in the repo is a prompt file. It’s not trying to be a full programming language replacement; it’s a fast way to express intent and run it on the active market. HelloBetfair AI prompt: Notice this one intentionally differs from the code examples: That’s actually a feature: prompts are great for quickly trying an idea, then “promoting” it into an F#/C#/VB trigger once it proves useful. Here are the real “interop” details you’ll notice when you compare the three codebases: The point of this “Hello Betfair” example is the side-by-side comparison. When you scan the three implementations, focus on these differences: If you want, tell me how you build/load DLL triggers in your setup (or point me to the solution files), and I’ll add a short “Build & load” section with the exact steps. Trading involves risk. Test in a safe environment first. This post is for educational purposes only. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: module BotTrigger #I @"C:\Program Files\BeloSoft\Bfexplorer\" #r "BeloSoft.Data.dll" #r "BeloSoft.Bfexplorer.Domain.dll" #r "BeloSoft.Bfexplorer.Service.Core.dll" #r "BeloSoft.Bfexplorer.Trading.dll" open BeloSoft.Bfexplorer.Domain open BeloSoft.Bfexplorer.Trading type MyStrategyBotTrigger (market : Market, selection : Selection, botName : string, botTriggerParameters : BotTriggerParameters, myBfexplorer : IMyBfexplorer) = inherit BotTriggerBase (market, selection, botName, botTriggerParameters, myBfexplorer) let getMySelection () = let fromPrice = defaultArg (botTriggerParameters.GetParameter<float> ("fromPrice")) 2.5 let toPrice = defaultArg (botTriggerParameters.GetParameter<float> ("toPrice")) 3.0 getFavouriteSelections market |> List.tryFind (fun mySelection -> let price = mySelection.LastPriceTraded price >= fromPrice && price <= toPrice ) interface IBotTrigger with member _this.Execute () = match getMySelection () with | Some mySelection -> TriggerResult.ExecuteActionBotOnSelection mySelection | None -> TriggerResult.EndExecutionWithMessage "No selection fulfills my criteria." member _this.EndExecution () = () Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: module BotTrigger #I @"C:\Program Files\BeloSoft\Bfexplorer\" #r "BeloSoft.Data.dll" #r "BeloSoft.Bfexplorer.Domain.dll" #r "BeloSoft.Bfexplorer.Service.Core.dll" #r "BeloSoft.Bfexplorer.Trading.dll" open BeloSoft.Bfexplorer.Domain open BeloSoft.Bfexplorer.Trading type MyStrategyBotTrigger (market : Market, selection : Selection, botName : string, botTriggerParameters : BotTriggerParameters, myBfexplorer : IMyBfexplorer) = inherit BotTriggerBase (market, selection, botName, botTriggerParameters, myBfexplorer) let getMySelection () = let fromPrice = defaultArg (botTriggerParameters.GetParameter<float> ("fromPrice")) 2.5 let toPrice = defaultArg (botTriggerParameters.GetParameter<float> ("toPrice")) 3.0 getFavouriteSelections market |> List.tryFind (fun mySelection -> let price = mySelection.LastPriceTraded price >= fromPrice && price <= toPrice ) interface IBotTrigger with member _this.Execute () = match getMySelection () with | Some mySelection -> TriggerResult.ExecuteActionBotOnSelection mySelection | None -> TriggerResult.EndExecutionWithMessage "No selection fulfills my criteria." member _this.EndExecution () = () COMMAND_BLOCK: module BotTrigger #I @"C:\Program Files\BeloSoft\Bfexplorer\" #r "BeloSoft.Data.dll" #r "BeloSoft.Bfexplorer.Domain.dll" #r "BeloSoft.Bfexplorer.Service.Core.dll" #r "BeloSoft.Bfexplorer.Trading.dll" open BeloSoft.Bfexplorer.Domain open BeloSoft.Bfexplorer.Trading type MyStrategyBotTrigger (market : Market, selection : Selection, botName : string, botTriggerParameters : BotTriggerParameters, myBfexplorer : IMyBfexplorer) = inherit BotTriggerBase (market, selection, botName, botTriggerParameters, myBfexplorer) let getMySelection () = let fromPrice = defaultArg (botTriggerParameters.GetParameter<float> ("fromPrice")) 2.5 let toPrice = defaultArg (botTriggerParameters.GetParameter<float> ("toPrice")) 3.0 getFavouriteSelections market |> List.tryFind (fun mySelection -> let price = mySelection.LastPriceTraded price >= fromPrice && price <= toPrice ) interface IBotTrigger with member _this.Execute () = match getMySelection () with | Some mySelection -> TriggerResult.ExecuteActionBotOnSelection mySelection | None -> TriggerResult.EndExecutionWithMessage "No selection fulfills my criteria." member _this.EndExecution () = () COMMAND_BLOCK: using BeloSoft.Bfexplorer.Domain; using BeloSoft.Bfexplorer.Trading; using Microsoft.FSharp.Core; namespace MyStrategyBotTrigger { public class MyStrategyBotTrigger(Market market, Selection selection, string botName, BotTriggerParameters botTriggerParameters, IMyBfexplorer myBfexplorer) : IBotTrigger { private (double FromPrice, double ToPrice) GetParameters() { var fromPriceOption = botTriggerParameters.GetParameter<double>("FromPrice"); var toPriceOption = botTriggerParameters.GetParameter<double>("ToPrice"); var fromPrice = FSharpOption<double>.get_IsSome(fromPriceOption) ? fromPriceOption.Value : 2.5; var toPrice = FSharpOption<double>.get_IsSome(toPriceOption) ? toPriceOption.Value : 3.0; return (fromPrice, toPrice); } private Selection GetMySelection() { var (fromPrice, toPrice) = GetParameters(); return MarketExtensionsModule.getFavouriteSelections(market) .FirstOrDefault(s => s.LastPriceTraded >= fromPrice && s.LastPriceTraded <= toPrice); } public TriggerResult Execute() { var mySelection = GetMySelection(); if (mySelection != null) { return TriggerResult.NewExecuteActionBotOnSelection(mySelection); } else { return TriggerResult.NewEndExecutionWithMessage("No selection fulfills my criteria."); } } public void EndExecution() { } } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: using BeloSoft.Bfexplorer.Domain; using BeloSoft.Bfexplorer.Trading; using Microsoft.FSharp.Core; namespace MyStrategyBotTrigger { public class MyStrategyBotTrigger(Market market, Selection selection, string botName, BotTriggerParameters botTriggerParameters, IMyBfexplorer myBfexplorer) : IBotTrigger { private (double FromPrice, double ToPrice) GetParameters() { var fromPriceOption = botTriggerParameters.GetParameter<double>("FromPrice"); var toPriceOption = botTriggerParameters.GetParameter<double>("ToPrice"); var fromPrice = FSharpOption<double>.get_IsSome(fromPriceOption) ? fromPriceOption.Value : 2.5; var toPrice = FSharpOption<double>.get_IsSome(toPriceOption) ? toPriceOption.Value : 3.0; return (fromPrice, toPrice); } private Selection GetMySelection() { var (fromPrice, toPrice) = GetParameters(); return MarketExtensionsModule.getFavouriteSelections(market) .FirstOrDefault(s => s.LastPriceTraded >= fromPrice && s.LastPriceTraded <= toPrice); } public TriggerResult Execute() { var mySelection = GetMySelection(); if (mySelection != null) { return TriggerResult.NewExecuteActionBotOnSelection(mySelection); } else { return TriggerResult.NewEndExecutionWithMessage("No selection fulfills my criteria."); } } public void EndExecution() { } } } COMMAND_BLOCK: using BeloSoft.Bfexplorer.Domain; using BeloSoft.Bfexplorer.Trading; using Microsoft.FSharp.Core; namespace MyStrategyBotTrigger { public class MyStrategyBotTrigger(Market market, Selection selection, string botName, BotTriggerParameters botTriggerParameters, IMyBfexplorer myBfexplorer) : IBotTrigger { private (double FromPrice, double ToPrice) GetParameters() { var fromPriceOption = botTriggerParameters.GetParameter<double>("FromPrice"); var toPriceOption = botTriggerParameters.GetParameter<double>("ToPrice"); var fromPrice = FSharpOption<double>.get_IsSome(fromPriceOption) ? fromPriceOption.Value : 2.5; var toPrice = FSharpOption<double>.get_IsSome(toPriceOption) ? toPriceOption.Value : 3.0; return (fromPrice, toPrice); } private Selection GetMySelection() { var (fromPrice, toPrice) = GetParameters(); return MarketExtensionsModule.getFavouriteSelections(market) .FirstOrDefault(s => s.LastPriceTraded >= fromPrice && s.LastPriceTraded <= toPrice); } public TriggerResult Execute() { var mySelection = GetMySelection(); if (mySelection != null) { return TriggerResult.NewExecuteActionBotOnSelection(mySelection); } else { return TriggerResult.NewEndExecutionWithMessage("No selection fulfills my criteria."); } } public void EndExecution() { } } } CODE_BLOCK: Imports BeloSoft.Bfexplorer.Domain Imports BeloSoft.Bfexplorer.Trading Imports Microsoft.FSharp.Core Public Class MyStrategyBotTrigger Implements IBotTrigger Private ReadOnly _market As Market Private ReadOnly _selection As Selection Private ReadOnly _botName As String Private ReadOnly _botTriggerParameters As BotTriggerParameters Private ReadOnly _myBfexplorer As IMyBfexplorer Public Sub New(market As Market, selection As Selection, botName As String, botTriggerParameters As BotTriggerParameters, myBfexplorer As IMyBfexplorer) _market = market _selection = selection _botName = botName _botTriggerParameters = botTriggerParameters _myBfexplorer = myBfexplorer End Sub Private Function GetParameters() As (FromPrice As Double, ToPrice As Double) Dim fromPriceOption = _botTriggerParameters.GetParameter(Of Double)("FromPrice") Dim toPriceOption = _botTriggerParameters.GetParameter(Of Double)("ToPrice") Dim fromPrice = If(OptionModule.IsSome(fromPriceOption), fromPriceOption.Value, 2.5) Dim toPrice = If(OptionModule.IsSome(toPriceOption), toPriceOption.Value, 3.0) Return (fromPrice, toPrice) End Function Private Function GetMySelection() As Selection Dim parameters = GetParameters() Dim fromPrice = parameters.FromPrice Dim toPrice = parameters.ToPrice Return MarketExtensionsModule.getFavouriteSelections(_market) _ .FirstOrDefault(Function(s) s.LastPriceTraded >= fromPrice AndAlso s.LastPriceTraded <= toPrice) End Function Public Function Execute() As TriggerResult Implements IBotTrigger.Execute Dim mySelection = GetMySelection() If mySelection IsNot Nothing Then Return TriggerResult.NewExecuteActionBotOnSelection(mySelection) Else Return TriggerResult.NewEndExecutionWithMessage("No selection fulfills my criteria.") End If End Function Public Sub EndExecution() Implements IBotTrigger.EndExecution End Sub End Class Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Imports BeloSoft.Bfexplorer.Domain Imports BeloSoft.Bfexplorer.Trading Imports Microsoft.FSharp.Core Public Class MyStrategyBotTrigger Implements IBotTrigger Private ReadOnly _market As Market Private ReadOnly _selection As Selection Private ReadOnly _botName As String Private ReadOnly _botTriggerParameters As BotTriggerParameters Private ReadOnly _myBfexplorer As IMyBfexplorer Public Sub New(market As Market, selection As Selection, botName As String, botTriggerParameters As BotTriggerParameters, myBfexplorer As IMyBfexplorer) _market = market _selection = selection _botName = botName _botTriggerParameters = botTriggerParameters _myBfexplorer = myBfexplorer End Sub Private Function GetParameters() As (FromPrice As Double, ToPrice As Double) Dim fromPriceOption = _botTriggerParameters.GetParameter(Of Double)("FromPrice") Dim toPriceOption = _botTriggerParameters.GetParameter(Of Double)("ToPrice") Dim fromPrice = If(OptionModule.IsSome(fromPriceOption), fromPriceOption.Value, 2.5) Dim toPrice = If(OptionModule.IsSome(toPriceOption), toPriceOption.Value, 3.0) Return (fromPrice, toPrice) End Function Private Function GetMySelection() As Selection Dim parameters = GetParameters() Dim fromPrice = parameters.FromPrice Dim toPrice = parameters.ToPrice Return MarketExtensionsModule.getFavouriteSelections(_market) _ .FirstOrDefault(Function(s) s.LastPriceTraded >= fromPrice AndAlso s.LastPriceTraded <= toPrice) End Function Public Function Execute() As TriggerResult Implements IBotTrigger.Execute Dim mySelection = GetMySelection() If mySelection IsNot Nothing Then Return TriggerResult.NewExecuteActionBotOnSelection(mySelection) Else Return TriggerResult.NewEndExecutionWithMessage("No selection fulfills my criteria.") End If End Function Public Sub EndExecution() Implements IBotTrigger.EndExecution End Sub End Class CODE_BLOCK: Imports BeloSoft.Bfexplorer.Domain Imports BeloSoft.Bfexplorer.Trading Imports Microsoft.FSharp.Core Public Class MyStrategyBotTrigger Implements IBotTrigger Private ReadOnly _market As Market Private ReadOnly _selection As Selection Private ReadOnly _botName As String Private ReadOnly _botTriggerParameters As BotTriggerParameters Private ReadOnly _myBfexplorer As IMyBfexplorer Public Sub New(market As Market, selection As Selection, botName As String, botTriggerParameters As BotTriggerParameters, myBfexplorer As IMyBfexplorer) _market = market _selection = selection _botName = botName _botTriggerParameters = botTriggerParameters _myBfexplorer = myBfexplorer End Sub Private Function GetParameters() As (FromPrice As Double, ToPrice As Double) Dim fromPriceOption = _botTriggerParameters.GetParameter(Of Double)("FromPrice") Dim toPriceOption = _botTriggerParameters.GetParameter(Of Double)("ToPrice") Dim fromPrice = If(OptionModule.IsSome(fromPriceOption), fromPriceOption.Value, 2.5) Dim toPrice = If(OptionModule.IsSome(toPriceOption), toPriceOption.Value, 3.0) Return (fromPrice, toPrice) End Function Private Function GetMySelection() As Selection Dim parameters = GetParameters() Dim fromPrice = parameters.FromPrice Dim toPrice = parameters.ToPrice Return MarketExtensionsModule.getFavouriteSelections(_market) _ .FirstOrDefault(Function(s) s.LastPriceTraded >= fromPrice AndAlso s.LastPriceTraded <= toPrice) End Function Public Function Execute() As TriggerResult Implements IBotTrigger.Execute Dim mySelection = GetMySelection() If mySelection IsNot Nothing Then Return TriggerResult.NewExecuteActionBotOnSelection(mySelection) Else Return TriggerResult.NewEndExecutionWithMessage("No selection fulfills my criteria.") End If End Function Public Sub EndExecution() Implements IBotTrigger.EndExecution End Sub End Class CODE_BLOCK: On the currently active Betfair market, sort the market selections by price in ascending order. Immediately execute the 'Bet 10 Euro' strategy on the first selection whose price is between 2.5 and 3.0 (inclusive). If there is no selection in this price range, do not execute any bet. Do not ask for further user confirmation—proceed automatically according to these criteria. Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: On the currently active Betfair market, sort the market selections by price in ascending order. Immediately execute the 'Bet 10 Euro' strategy on the first selection whose price is between 2.5 and 3.0 (inclusive). If there is no selection in this price range, do not execute any bet. Do not ask for further user confirmation—proceed automatically according to these criteria. CODE_BLOCK: On the currently active Betfair market, sort the market selections by price in ascending order. Immediately execute the 'Bet 10 Euro' strategy on the first selection whose price is between 2.5 and 3.0 (inclusive). If there is no selection in this price range, do not execute any bet. Do not ask for further user confirmation—proceed automatically according to these criteria. - F# script (or DLL): run a BotTrigger directly from .fsx source (and yes, you can also compile F# to a DLL) - C# / VB.NET plugin: compile a DLL and load it into bfexplorer - AI prompt: describe the logic in natural language and let an LLM execute it via bfexplorer’s MCP integration - Place a back bet (or execute an action bot) if a selection price is within a given range. - executes an action on that selection, or - ends with a message if nothing matches. - Overview: ../../src/HelloBetfair/README.md - F# script: ../../src/HelloBetfair/FSharp/MyStrategyBotTrigger.fsx - C# trigger: ../../src/HelloBetfair/CSharp/MyStrategyBotTrigger/MyStrategyBotTrigger.cs - VB.NET trigger: ../../src/HelloBetfair/VisualBasic/MyStrategyBotTrigger/MyStrategyBotTrigger.vb - AI prompt: ../../src/HelloBetfair/AI/MyStrategy.md - No DLL build: iterate fast - the selection filtering reads cleanly (tryFind + pattern matching) - parameter defaults are one-liners (defaultArg ... 2.5) - bfexplorer APIs expose F# option, so you’ll see FSharpOption<T> interop - the parameter names are FromPrice / ToPrice (PascalCase) - it sorts selections by price - it uses the same range ($[2.5, 3.0]$) - it executes a named strategy (“Bet 10 Euro”) on the first match - Parameters are just string keys: botTriggerParameters.GetParameter<T>(name) looks up a string in a dictionary. Pick names you like (for example FromPrice/ToPrice) and keep them consistent across languages. - F# discriminated unions (DU) show up as generated classes in C#/VB: TriggerResult is an F# DU. In F# you return DU cases directly (e.g. TriggerResult.ExecuteActionBotOnSelection mySelection). In C#/VB you construct the DU using generated helpers (e.g. TriggerResult.NewExecuteActionBotOnSelection(mySelection) / NewEndExecutionWithMessage(...)). - F# option types require interop: when you read optional values from F#, C#/VB will see FSharpOption<T> (or use OptionModule.IsSome(...) in VB). That’s why the C#/VB examples look a bit more “ceremonial” around parameters. - Trigger vs execution separation is intentional: these examples return ExecuteActionBotOnSelection rather than placing orders inline. In bfexplorer, that separation often makes strategies easier to reuse and test (selection logic in the trigger, execution rules in the action bot). - Expression of the same rule: F# uses List.tryFind + pattern matching; C#/VB use LINQ-style FirstOrDefault + if. - F# DU / option interop: TriggerResult and option feel native in F#, but become generated classes/helpers in C#/VB. - Packaging and workflow: .fsx is great for iteration; DLLs are great when you want a compiled artifact (and you can choose either approach in F#). - AI prompt as a contrast: it’s not about syntax at all—it’s about describing intent and letting bfexplorer/MCP drive execution.