月度归档:2015年12月

在止损最小时开仓,在市场疯狂时平仓

这简单的两句话,就构成了一个简易的交易系统。

首先谈谈止损。是否会止损,是衡量一个交易员是否入门的标准。交易最大的错误,就是开仓的时候不明确设止损。没有止损保护的仓位,会让自己的交易账户失控,变得和赌博无异。但是,胡乱的设置止损对账户的伤害也很大。比如根据盈亏额度或者比例来止损,往往导致止损在震荡的高点或者低点。或者在大行情到来之前,确因为一个小回撤就止损出局。设置了止损,不严格执行也是白搭。严格执行止损不仅包括止损条件到了就平仓,还有要严格执行交易计划,不随便更改止损位置。想必有经验的交易者对这几句话都有感触。

什么是成功的止损?简单的来说,成功的止损就是在止损后,市场趋势反转,或者给你带来一个更好的进场位置。那么,最好的止损位置,就是最容易明确判断市场走向的位置。如何判断市场的转折点,就要靠技术分析了。技术分析通过趋势线,压力线,形态或者指标来判断市场的转折,往往是很明确的。价值投资因为信息不透明或者很难衡量信息的分量,所以很难确定止损位置,这是价值投资的劣势。

那么很显然,止损最小的时候就是最好的进场时候,这里说的止损当然是成功的止损,瞎止损不算。较小的止损我们能够通过较小的风险来博取较大的收益。我们需要做的就是寻找最小止损的时候交易。什么时候止损最小,就是技术分析的范畴了,我这里说几个把握比较大的方法:

缩量横盘很久后向一个方向突破。横盘时因为市场多空力量处于势均力敌之中,向一个方向突破是因为有新的力量打破了这一僵局。所谓横有多长,竖有多高。在价格突破近期的高点或低点开仓,止损很简单,市场突破后又回到原来的震荡区间,说明是假突破。这种例子在市场中非常常见。

三角整理区突破。三角整理区一般发生在趋势的进行的中间,也可以构成市场的反转,突破后往往意味着趋势的继续或反转。这时候往往是一个比较好的入场时机。在突破时开仓,止损很简单,没有有效突破止损。

压力/支撑线突破。压力线就是把市场连续几个波峰的高点练成的一条线,支撑线就是市场连续几个低点连成的一条线。最明显的例子就是2014年底上证指数突破数年的压力线,后面的事情大家都知道了:

新高新低突破。前期的高点/低点往往会变成重要的压力/支撑位,如果突破该点位形成新低或新高,说明有重要的力量打破该预期,顺着突破的方向开仓。《海龟交易法则》一书中所用的交易系统就是一套新高新低突破交易系统。还以上证指数为例,在今年三月份上证指数突破3400点得时候,就和朋友说,这可是能最好的买点了(但指数并不代表个股,指数突破不代表个股突破,比如金三胖在当时表现并不好,这也是股市的复杂之处):


上面说的这几种方法,所用的周期越长越有效,最少应该看日K线。周期越长越难被人为操纵,其突破往往代表重要趋势的变化。

俗话说,会买的是徒弟,会卖的是师傅。平仓了才能把账面利润变成实际利润。什么时候卖,确实是一个很复杂的问题。卖早了,可能价格涨了10倍,自己只赚了1倍,卖晚了,辛辛苦苦的利润化为灰烬。我也没法告诉你这个问题的答案,因为我也不知道。但在市场疯狂地时候,卖总是没错的。市场成交量放大,价格飞速上涨的时候,就是市场疯狂的时候。比如今年5月份的A股,比如13年底的比特币。这时候就是要最小心的时候了,一旦价格出现巨大回落,就要准备离场了。

2014~2015 年的A股:

2013~2014 年的比特币:

2015年末的比特币:

如何使用Mysql正确的处理财务数据

财务数据相比于普通的互联网应用数据,对数据的一致性有更高的要求。因为涉及到用户金钱的流动,出现问题就意味金钱和声誉上的损失。在用 Mysql 处理财务数据时,我认为应该遵循以下原则:

  1. 使用 DECIMAL 数据类型存储金额。因为浮点数精度是有限的,并且无法精确的表示一些数字。应用程序也应该使用 Decimal 函数库来进行金额的加减乘除的运算,比如 Python 的 decimal 模块,C++ 的 boost Multiprecision 库。
  2. 使用事务来更新数据库。涉及到数据库多个记录更新时,事务能够做到要么全部成功,要么全部失败,这保证了数据的一致性。Mysql 使用事务需要 InnoDB 引擎。
  3. 更新数据库时使用悲观锁。更新数据前使用 SELECT …  FOR UPDATE; 来查询,这样防止并发的请求读到脏数据,导致数据错乱。虽然加锁会影响性能,但为了数据的一致性也是值得的。
  4. 记录资金变化的流水日志。不能简单的只记录用户的金额,还要记录每笔资金的来龙去脉,包括变化的大小、时间、业务、更新前金额、更新后金额、备注等,另外还有记录业务ID防止重复更新金额。这样在对账的时候才能有理有据。

下面以一个完整的示例来说明如何设计一个完备的记账系统用来记录人民币余额,这里精度只需要2位小数就可以了。

假设已经有一个用户表,每个用户有一个唯一ID。我们需要创建两张表,一张余额表,一张流水表:

CREATE TABLE `balance` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `balance` decimal(20,2) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE `history` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `amount` decimal(20,2) NOT NULL,
  `befor` decimal(20,2) NOT NULL,
  `after` decimal(20,2) NOT NULL,
  `business` varchar(30) NOT NULL,
  `business_id` varchar(100) NOT NULL,
  `detail` text
) ENGINE=InnoDB;

为了加快查询速度,另外为了有效利用 InnoDB 的行级锁,我们需要给两张表加上联合索引。另外,我们需要保证流水记录中 user_id, item, business, business_id 的组合是唯一的,避免重复更新数据。

ALTER TABLE balance ADD INDEX `user_item_idx` (`user_id`, `item`);

ALTER TABLE history ADD INDEX `user_item_idx` (`user_id`, `item`);

ALTER TABLE history ADD UNIQUE update_unique (user_id, item, business, business_id);

假设这时候用户 ID 为 1 的用户充值了 100 元人民币,我们需要把用户的人民币余额加上 100 元,我们要如何处理呢?

这里我们还需要一个充值表用来保存用户的充值记录,同时,我们再创建一个提现表来表示用户提现记录,如下所示:

CREATE TABLE `deposit` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `amount` decimal(20,2) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE `withdraw` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `amount` decimal(20,2) NOT NULL
) ENGINE=InnoDB;

首先,开始一个事务,创建充值记录:

START TRANSACTION;

INSERT INTO deposit VALUES (NULL, 1, 'CNY', '100')

插入成功后,我们可以使用 MYSQL 的 API 获取到上次插入后生成的自增 ID 的值,假设这里也为 1. 然后我们需要从数据库查询当前余额。查询的时候注意要使用 FOR UPDATE 来锁住该记录,避免其它并发的请求读到脏数据。即使当前该记录不存在,在事务提交之前,其它读请求仍然被阻塞读不到数据的。

SELECT id, balance FROM balance where user_id = 1 and item = 'CNY' FOR UPDATE;

由于用户是新注册的,查询到的数据为空,所以我们创建新的记录:

INSERT INTO balance VALUES (NULL, 1, 'CNY', '100');

然后我们要在流水表中记录下这次的变更操作:

INSERT INTO history VALUES (NULL, NULL, 1, 'CNY', '100', '0', '100', 'deposit', '1', '');

最后提交事务:

COMMIT;

事务提交后,所有的数据都写入到数据库中了,中间如果有异常发生,则执行

ROLLBACK;

来放弃所有的变更。

假设过了一段时间用户要求提现 50 元,操作流程如下:

START TRANSACTION;

SELECT id, balance FROM balance where user_id = 1 and item = 'CNY' FOR UPDATE;

假设读出的 id 也为 1. 由于用户之前充值了 100 元还没有使用,所以余额是 100,大于 50 满足提现条件。如果不满足的话需要执行 ROLLBACK. 然后:

INSERT INTO withdraw VALUES (NULL, 1, 'CNY', '50');

UPDATE balance set balance = '50' WHERE id = 1;

INSERT INTO history VALUES (NULL, NULL, 1, 'CNY', '-50', '100', '50', 'withdraw', '1', '');

COMMIT;

这就完整的实现了整个充值和提现的流程。

当然,这里示例的充值和提现是最简化的流程,实际业务中,充值和提现往往涉及到多种状态的流转。并且在提现中,用户发起提现和实际进行转账不可能是同时进行的,中间可能会取消操作,直接扣除资金不太妥当,更妥当的做法是把待提现的资金冻结起来,冻结操作其实就是创建一个新的 item: CNY_FREEZE, 在 CNY 上扣减,然后在 CNY_FREEZE 上增加来实现。转账后再从 CNY_FREEZE 中扣除。如果取消操作则取消冻结,从 CNY_FREEZE 扣减,增加到 CNY 上面,这样整个流程在流水日志上都有体现。