跳转至

C# 用于算法交易

什么是 C#

C# 是一种面向对象的编程语言,用于构建各种服务和应用程序。 其清晰、结构化的语法使其易于学习和阅读,即使是初学者也能轻松上手,同时提供了编写紧凑且可重用代码的结构化方法。

一分钟内掌握 C# 算法!

  • 即使您以前从未使用过 C#,创建和部署 您的第一个机器人指标 也只需几分钟。
  • 由于 C# 易于使用,您可以快速重写本文档中的任何代码片段以满足您的需求。
  • 使用 C# 时,您可以访问包含预定义类和方法的大量库。 这些方法可以高效处理常见任务,让您专注于解决复杂的交易问题。
  • 在 C# 中,您可以编写在执行时不会阻塞服务器线程的代码。 换句话说,您可以同时启动某些任务与其他任务。

什么是 .NET?

为了使 C# 程序运行,其源代码必须编译为中间语言 (IL)。 此 IL 代码遵循公共语言基础结构 (CLI) 规范,并在运行时编译为本机机器指令。 .NET 框架提供了一个基于广泛类库和公共语言运行时 (CLR) 的虚拟执行环境,CLR 是 Microsoft 对 CLI 的实现。

在不深入技术细节的情况下,.NET 实现了以下功能:

  • 促进应用程序的开发和执行:.NET SDK 已经包含多个内置编译器和构建引擎,无需创建任何自定义解决方案。
  • 提供运行时库:当向代码中添加新数据类型或集合时,您经常会发现 .NET 已经提供了适合该任务的类和方法。

C# 基础

数据类型和变量声明

数据类型是分类数据的一种方式,以便 C# 确切知道如何处理变量和属性。 在变量/属性声明中,数据类型始终位于变量/属性名称之前。

1
string developer = "I am developing cBots, plugins, and indicators!";

或者,您可以使用 var 关键字来避免指定数据类型。

1
var developer = "I am developing cBots, plugins, and indicators!";

对象和类

将对象视为有形或无形实体的抽象。 这些实体可以具有某些特征(属性)并可以执行各种操作(方法)。 反过来,类作为对象创建的模板。

由于 C# 是一种面向对象的语言,类也可以从其他类继承属性和方法。 考虑以下示例,其中我们声明了一个从 Robot 类继承的 NewBot 类。 我们还定义了一些新的类属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* We use the colon sign to designate inheritance.
Additionally, we used expressions in square brackets to
specify the parameters that apply to the entire class */
[Robot(AccessRights = AccessRights.None)]
public class NewBot : Robot
{
    /* We declare a custom class property and make it
    read-only. */
    public string CustomProperty { get; }

    /* In this declaration, we define the default value of a
    custom parameter. */
    [Parameter("BotName", DefaultValue = "Traders First!")]

    /* We declare the BotName property which is changeable via
    the "BotName" parameter. */
    public string BotName { get; }

    /* We also declare the BotComment parameter.
    It can be both read and set. */
    [Parameter("BotComment", DefaultValue = "Our super-duper bot!")]
    public string BotComment { get; set; }
}

数据类型

由于 C# 是一种强类型语言,因此在声明变量和类属性时必须指定数据类型。 简而言之,数据类型构成了具有不同行为集的唯一类。 考虑以下示例,其中为不同的类属性和相关参数指定了各种数据类型。

1
2
[Parameter("Price")]
public DataSeries Source { get; }

在上面的代码中,DataSeries 数据类型表示一个只读值列表,因此通常用于表示市场价格。

当需要创建多选项参数时,您可以使用内置的 enum 数据类型,如下所述。 您可以将 enum 视为包含各种常量的特殊类。

1
2
3
4
5
6
7
8
9
public enum Option
{
    First,
    Second,
    Third
}

[Parameter("Option", DefaultValue = Option.Third)]
public Option SelectedOption { get; set; }

要访问 enum 中包含的常量,请使用以下语法。

1
var newOption = Option.First;

如果参数或属性需要是数字,您最常使用 int(整数)或 double(小数)数据类型。 虽然 double 类型的精度低于 decimal 类型,但它也较少占用资源。

1
2
[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
public int Periods { get; set; }

最后,bool 类型通常表示是或否的输入。 是和否分别对应 truefalse

1
2
[Parameter("Message", DefaultValue = true)]
public bool DisplayChartMessage { get; set; }

命名空间

命名空间充当类的指定集合。 一旦将类分配给命名空间,以后就可以使用 Namespace.ClassName 表示法访问它。 我们将 NewBot 分配给 CoolTradingBots 命名空间。

1
2
3
4
5
6
7
8
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ...
    }
}

类方法

类方法在类声明之后定义。 我们的 NewBot 类的所有对象都将能够调用 CustomTradeOperation() 方法。 它接受两个参数,即 Symbol 对象和 double 对象。 我们的方法应该接受一个符号并为指定的交易量执行某种交易操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ... Defining the class parameters.

        // We declare the CustomTradeOperation method. 
        protected override void CustomTradeOperation(string symbolName, double volume)

        {
            // This space is for declaring the method logic. 
        }
    }
}

类库

类库(以及随后的类方法)可以通过其命名空间访问,如下例所示。

1
CoolTradingBots.NewBot.CustomTradeOperation(symbolName, 10000)

或者,您可以在代码开头输入 using 关键字以指定某些命名空间并避免冗余。 考虑以下代码片段。

1
2
3
using CoolTradingBots;

NewBot.CustomTradeOperation(symbolName, 10000)

条件语句

要使用条件语句,请使用关键字后跟圆括号中的表达式。 以下示例使用 if 关键字来完成我们的 CustomTradingOperation() 方法。 为此,我们使用 Analytics.Actions 命名空间中定义的 EvaluateMarket() 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ... Defining the class parameters.

        protected override void CustomTradeOperation(string symbolName, double volume)
        {
            // We declare and initialize the 'result' variable.
            var result = Analytics.Action.EvaluateMarket();

            // We use a conditional statement based on the IsSuccessful property.
            if (result.IsSuccessful)
            {
                Print("Operation successful!")
            }
        }
    }
}

集合

集合定义为可以存储特定类的一个或多个对象的容器。

集合是完全可索引的,这意味着它们的成员可以通过在方括号中传递某个 integer 值来访问。 考虑以下示例,其中 Calculate() 方法打印通过其索引访问的柱的属性。

1
2
3
4
5
public override void Calculate(int index)
{
       var bar = Bars[index];
       Print($"{bar.Open} | {bar.High} | {bar.Low} | {bar.Close}");
}

创建 cTrader 扩展

在下面的代码片段中,我们仅使用上述章节中涵盖的知识创建一个基本的交易机器人。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System; 

namespace Spotware.cBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class SuperAwesomeBot : Robot
    {

        /* In the below method, we define the operation(s) that our bot should
        perform when it is launched. */
        protected override void OnStart()
        {
            var result = ExecuteMarketOrder(TradeType.Buy, symbolName, 10000);

            /* We use a conditional statement using the IsSuccessful property of the
            TradeResult object. */
            if (result.IsSuccessful)
            {
                var position = result.Position;
                Print ("Position entry price is {0}", position.EntryPrice);
            } 
        }
    }
}

同步和异步操作

之前所述,C# 完全支持异步操作。 下图定义了同步执行中如何执行交易活动的基本示例。

graph TD
    A(遇到技术信号) ==> B(执行市价单) 
    B ==> C(达到止盈/止损) 
    C ==> D(平仓)
    D ==> A

同步执行有一个重要的缺点。 在上面的例子中,执行市价订单的操作完全占用了所有服务器线程,这意味着在此操作完成之前,您的交易机器人无法执行任何其他操作。

当您想要快速响应市场事件和波动时,这并不理想。 理想情况下,您的机器人应该能够存储各种操作或暂停某些任务,以便参与其他更紧迫的活动。 我们将扩展我们的示例,以更好地反映异步操作是如何执行的。

graph TD
    A([遇到技术信号]) ==> B([下达买入订单]) & C([下达对冲卖出订单]) ==> D([订单达到止盈/止损]) 
    D ==> E([平仓])
    E ==> A

在上面的例子中,我们的交易机器人同时在两个方向下单以对冲其头寸。 与同步操作相比,无需等待一个操作完成后再进行另一个操作。 这显著扩展了开发人员创建高效可靠交易机器人的机会。

请注意,异步执行与多线程不同:

  • 在异步执行中,所有任务都在同一线程上启动。 当它们被存储或暂停时,它们会释放这个线程,当它们稍后继续执行时,会在从线程池中选择的不同线程上进行。
  • 在多线程中,所有任务都在不同的线程上启动,并在其初始线程上继续执行。 没有线程切换。

cTrader 从不并行调用您的方法,因此您无需担心多线程问题。

Image title